Task 1: Exploratory data analysis
library(readxl)
library(Seurat)
library(tidyverse)
library(chameleon)
library(randomForest)
Preprocess metadata
Load scRNA-seq data object and metadata.
galen_aml <- readRDS("reanalyze-aml2019/Seurat_AML.rds")
donor_metadata <-
readxl::read_xlsx(
"ScienceDirect_files_21Nov2024_08-09-30.987/1-s2.0-S0092867419300947-mmc1.xlsx"
)
Load HSPC cell types annotated by backspin cluster and add to
metadata of Seurat object. From aml2019
github repo.
backspin_celltypes <-
read_tsv("aml2019/04 Random forest classifier/BM_6915cells.BackSPIN.txt")
backspin_celltypes <-
column_to_rownames(backspin_celltypes, var = "cell") %>% dplyr::rename(backspin_celltype = cluster)
rownames(backspin_celltypes) <- gsub("-", "\\.", rownames(backspin_celltypes))
galen_aml <- AddMetaData(galen_aml, metadata = backspin_celltypes)
head(galen_aml@meta.data)
head(donor_metadata)
Metadata included in Seurat object - orig.ident (patient ID + day
from diagnosis) - NumberOfReads - CyclingScore - CyclingBinary -
MutTranscripts (number of transcript for wt allele) - WtTranscripts
(number of transcript for mutated allele) - PredictionRefined
(classification of cells into malignant, healthy and uncertain) -
CellType (classification into 21 healthy and malignant cell types) -
nCount_RNA - nFeature_RNA
Remove cells and metadata associated to cell lines.
galen_aml$SampleClass <-
ifelse(
grepl("BM", galen_aml$orig.ident),
"HealthyDonor",
ifelse(
grepl("OCI.AML3|MUTZ3", galen_aml$orig.ident),
"CellLine",
"AmlDonor"
)
)
galen_aml_donors <- subset(galen_aml, SampleClass != "CellLine")
donor_metadata <- donor_metadata %>%
filter(!Sample %in% c("OCI-AML3", "MUTZ3"))
Correct value in blast count metadata. “1% (76% promono-cytes)” is
considered as blast count of 76% in the paper (Fig. 2b).
donor_metadata$`Blast count` <-
as.numeric(gsub("<5\\%", "0.05", gsub(
"<1\\%",
"0.01",
gsub(".*76\\%.*", "0.76", donor_metadata$`Blast count`)
)))
Warning: NAs introduced by coercion
donor_metadata$Age <- as.numeric(donor_metadata$Age)
Merge donor metadata into Seurat object metadata.
# match sample ID between Seurat object and metadata table
donor_metadata <- donor_metadata %>%
mutate(SampleId = gsub("CD", "", gsub(" ", "\\.", gsub(
"\\+", "p", gsub("-", "n", Sample)
)))) %>%
mutate(SampleId = ifelse(
grepl("BM", SampleId),
SampleId,
paste(SampleId, `Days from diagnosis`, sep = ".")
))
# merge into seurat object metadata, preserve order of cells
tmp <-
merge(
rownames_to_column(galen_aml_donors@meta.data),
donor_metadata,
by.x = "orig.ident",
by.y = "SampleId",
all.x = T
)
tmp <- column_to_rownames(tmp, "rowname")
galen_aml_donors@meta.data <- tmp[colnames(galen_aml_donors),]
Sex distribution
Tettero, J.M., Cloos, J. & Bullinger, L. Acute myeloid leukemia:
does sex matter?. Leukemia 38, 2329–2331 (2024). https://doi.org/10.1038/s41375-024-02435-z
AML is more frequent in males (ca. 4.5 per 100,000) vs females (ca.
3.0 per 100,000). A number of studies showing differences in treatment
response in males and females demonstrates the importance to consider
sex.
The van Galen dataset only has male healthy donors and is biased
towards male AML donors (62.5%). Slightly more pronounced when
considering AML samples (66.7%).
Only cells from healthy donors and genotyped malignant cells from AML
cells were used for training the malignant vs. normal classifier. I.e.,
all healthy cells in the training data were male.
This would usually result in a classification of all female cells as AML
cells. However, there is also a male AML samples with loss of Y
(AML707B), which makes sex more ambiguously linked to malignant status.
Furthermore, expression of sex-specific genes could be low/sparse, and
not all cells might be distinguishable by sex based on their
expression.
# donors
p1 <- donor_metadata %>%
select(Sample, Gender) %>%
mutate(Sample = gsub(" .*", "", Sample)) %>%
unique() %>%
mutate(Group = ifelse(grepl("BM", Sample), "Healthy", "AML")) %>%
group_by(Group, Gender) %>%
summarize(n_donors = n()) %>%
ggplot(aes(fill = Gender, y = n_donors, x = Group)) +
geom_bar(stat = "identity") +
theme_bw() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "None",
panel.grid = element_blank()
)
`summarise()` has grouped output by 'Group'. You can override using the `.groups` argument.
# samples
p2 <- donor_metadata %>%
select(Sample, `Days from diagnosis`, Gender) %>%
unique() %>%
mutate(Group = ifelse(grepl("BM", Sample), "Healthy", "AML")) %>%
group_by(Group, Gender) %>%
summarize(n_samples = n()) %>%
ggplot(aes(fill = Gender, y = n_samples, x = Group)) +
geom_bar(stat = "identity") +
theme_bw() +
theme(panel.grid = element_blank(),
axis.text.x = element_text(angle = 45, hjust = 1))
`summarise()` has grouped output by 'Group'. You can override using the `.groups` argument.
ggpubr::ggarrange(p1, p2, align = "h", widths = c(1, 1.3))

Cancer driver mutations
Plot which sample has which cancer driver gene mutated (see Fig.
2B).
AML722B BCOR in paper Fig. 2b but not in supplementary metadata.
All samples have a unique combination of mutations.
sample_mutation_matrix <- donor_metadata %>%
filter(
!`RHP Mutations` %in% c("Not performed", "None Detected", "Unknown", "NA") &
!is.na(`RHP Mutations`)
) %>%
mutate(driver_mutations = `RHP Mutations`) %>%
separate_longer_delim(cols = driver_mutations, "/// ") %>%
mutate(driver_mutations = gsub(" .*", "", driver_mutations)) %>%
# unique for samples with multiple mutations in same gene
unique() %>%
# count number of samples for each gene and sort by that
group_by(driver_mutations) %>%
mutate(n_samples = n()) %>%
arrange(desc(n_samples)) %>%
mutate(value = 1)
sample_mutation_matrix <- sample_mutation_matrix %>%
mutate(
driver_mutations = factor(
driver_mutations,
levels = unique(sample_mutation_matrix$driver_mutations),
ordered = T
),
Sample = factor(
Sample,
level = unique(sample_mutation_matrix$Sample),
ordered = T
)
)
# mutations
p1 <- sample_mutation_matrix %>%
ggplot(aes(x = driver_mutations, y = Sample)) +
geom_tile(fill = "darkred") +
theme_bw() +
theme(axis.text.x = element_text(
angle = 90,
vjust = 0.5,
hjust = 1
)) +
theme(panel.grid = element_blank())
# rearrangements
p2 <- sample_mutation_matrix %>%
ggplot(aes(x = `Common translocation`, y = Sample)) +
geom_tile(fill = "darkred") +
theme_bw() +
theme(axis.text.x = element_text(
angle = 90,
vjust = 0.5,
hjust = 1
)) +
theme(
panel.grid = element_blank(),
axis.text.y = element_blank(),
axis.ticks.y = element_blank()
) +
ylab("")
ggpubr::ggarrange(
p1,
p2,
ncol = 2,
nrow = 1,
align = "h",
widths = c(4, 1)
)

Cell type composition
Cell types present in data.
unique(galen_aml_donors$CellType)
[1] HSC Prog earlyEry lateEry GMP ProMono Mono cDC pDC ProB Plasma T CTL B
[15] NK HSC-like Prog-like GMP-like ProMono-like Mono-like cDC-like
Levels: HSC Prog earlyEry lateEry GMP ProMono Mono cDC pDC ProB B Plasma T CTL NK HSC-like Prog-like GMP-like ProMono-like Mono-like cDC-like
New metadata column whether cell is a cancer celltype or healthy.
Color scheme to avoid rainbow colors.
cell_types <- unique(galen_aml_donors@meta.data$CellType)
cell_type_colors <-
sample(chameleon::distinct_colors(n = length(cell_types))$name,
size = length(cell_types))
cell_type_colors <- setNames(cell_type_colors, cell_types)
Create more “coarse” cell type labels. Lymphoid and erythroid
(non-malignant only), and Myeloid/HSC, Myeloid/HSC-like.
lymphoid_celltypes <- c("Plasma",
"NK",
"ProB",
"B",
"T",
"CTL",
"pDC")
galen_aml_donors@meta.data <- galen_aml_donors@meta.data %>%
mutate(IsCancerCelltype = ifelse(grepl("-like", CellType), T, F)) %>%
mutate(CellTypeGeneral = ifelse(
CellType %in% lymphoid_celltypes,
"Lymphoid",
ifelse(
IsCancerCelltype,
"Myeloid/HSPC-like",
ifelse(grepl("Ery", CellType), "Erythroid", "Myeloid/HSPC")
)
))
Visualize cell type proportions across samples.
galen_aml_donors@meta.data %>%
group_by(orig.ident) %>%
count(CellType) %>%
ggplot(aes(fill = CellType, y = n, x = orig.ident)) +
geom_bar(position = "fill", stat = "identity") +
scale_fill_manual(values = cell_type_colors[unique(galen_aml_donors@meta.data$CellType)]) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
panel.grid = element_blank())

galen_aml_donors@meta.data %>%
group_by(orig.ident) %>%
count(IsCancerCelltype) %>%
ggplot(aes(fill = IsCancerCelltype, y = n, x = orig.ident)) +
geom_bar(position = "fill", stat = "identity") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
panel.grid = element_blank())

galen_aml_donors@meta.data %>%
group_by(orig.ident) %>%
count(PredictionRefined) %>%
ggplot(aes(fill = PredictionRefined, y = n, x = orig.ident)) +
geom_bar(position = "fill", stat = "identity") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
panel.grid = element_blank())

galen_aml_donors@meta.data %>%
group_by(orig.ident) %>%
count(CellTypeGeneral) %>%
ggplot(aes(fill = CellTypeGeneral, y = n, x = orig.ident)) +
geom_bar(position = "fill", stat = "identity") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
panel.grid = element_blank())

Myeloid and HSPCs are the cells that are difficult to distinguish
between healthy and malignant in AML cells. This is the actual difficult
prediction task of the classifier. Visualize the proportion of these
cells in the samples.
galen_aml_donors@meta.data %>%
filter(CellTypeGeneral %in% c("Myeloid/HSPC", "Myeloid/HSPC-like")) %>%
group_by(orig.ident, CellTypeGeneral) %>%
summarize(n_cells = n()) %>%
ggplot(aes(x = orig.ident, y = n_cells, fill = CellTypeGeneral)) +
geom_bar(stat = "identity", position = "fill") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
panel.grid = element_blank())
`summarise()` has grouped output by 'orig.ident'. You can override using the `.groups` argument.

Proportion of cycling cells is not higher in AML samples at diagnosis
than in healthy samples. AML is a disease of differentiation. The
accumulation of blasts in the bone marrow is the problem, not hyper
proliferation, as in other cancers.
galen_aml_donors@meta.data %>%
filter(`Days from diagnosis` == "D0" |
SampleClass == "HealthyDonor") %>%
group_by(orig.ident, SampleClass) %>%
count(CyclingBinary) %>%
ggplot(aes(fill = CyclingBinary, y = n, x = orig.ident)) +
geom_bar(position = "fill", stat = "identity") +
facet_grid( ~ SampleClass, scales = "free_x", space = "free") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1), panel.grid = element_blank())

Genotype information
Classify cells based on their genotype information into malignant and
healthy cells.
I classify all cells with a mutant transcript detected as malignant
cells.
All other cells from AML samples with a wt allele detected could be
classified as healthy.
However, this is imperfect, especially for heterozygous dominant
negative mutations.
Furthermore, due to clonality, there might be cells with wt allele
detected and mut allele missed.
Both can lead to false positive healthy cells.
Therefore, only using cells from AML donors genotyped as mutant and
healthy cells from healthy donors in the classifier (as done by van
Galen).
“The second classifier is used for determining if a cell for which we
did not detect a mutant transcript is malignant or normal, based on its
similarity to normal and malignant cells (i.e., cells from healthy BM
and HSC to myeloid-like cells from tumor samples for which we detected
mutant transcripts).”
galen_aml_donors@meta.data <- galen_aml_donors@meta.data %>%
mutate(genotype = ifelse(
!is.na(MutTranscripts) &
MutTranscripts != "",
"malignant",
ifelse(
!is.na(WtTranscripts) &
WtTranscripts != "",
"healthy",
"not_detected"
)
))
Plot 1: Cells with mut detected, only wt detected or no coverage,
compared to expected blast count.
Plot 2: Cells usable to trian malignant vs. healthy classifier.
There are multiple samples and patients with no or almost no
malignant genotyped cells that can be used for training.
Therefore, the classifier needs to be generalizable to other samples and
mutations.
Overfitting to specific patients would be bad.
p1 <- galen_aml_donors@meta.data %>%
group_by(orig.ident, genotype) %>%
summarise(n_cells = n()) %>%
ungroup() %>%
ggplot(aes(fill = genotype, y = n_cells, x = orig.ident)) +
geom_bar(stat = "identity") +
theme_bw() +
theme(axis.text.x = element_blank(),
panel.grid = element_blank()) +
scale_x_discrete(drop = FALSE) +
xlab(element_blank())
`summarise()` has grouped output by 'orig.ident'. You can override using the `.groups` argument.
p2 <- galen_aml_donors@meta.data %>%
select(`Blast count`, orig.ident) %>%
unique() %>%
ggplot(aes(y = `Blast count`, x = orig.ident)) +
geom_bar(stat = "identity") +
theme_bw() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
panel.grid = element_blank()) +
scale_x_discrete(drop = FALSE) +
xlab(element_blank())
ggpubr::ggarrange(
p1,
p2,
align = "v",
heights = c(2, 1),
ncol = 1,
nrow = 2
)
Warning: Removed 6 rows containing missing values or values outside the scale range (`geom_bar()`).

p1 <- galen_aml_donors@meta.data %>%
mutate(Sample = as.factor(Sample)) %>%
filter(genotype == "malignant" |
SampleClass == "HealthyDonor") %>%
group_by(Sample, genotype) %>%
summarise(n_cells = n()) %>%
ungroup() %>%
ggplot(aes(fill = genotype, y = n_cells, x = Sample)) +
geom_bar(stat = "identity") +
theme_bw() +
scale_x_discrete(drop = FALSE) +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
panel.grid = element_blank()) + scale_x_discrete(drop = FALSE)
`summarise()` has grouped output by 'Sample'. You can override using the `.groups` argument.Scale for x is already present.
Adding another scale for x, which will replace the existing scale.
p2 <- galen_aml_donors@meta.data %>%
filter(genotype == "malignant" |
SampleClass == "HealthyDonor") %>%
group_by(orig.ident, genotype) %>%
summarise(n_cells = n()) %>%
ungroup() %>%
ggplot(aes(fill = genotype, y = n_cells, x = orig.ident)) +
geom_bar(stat = "identity") +
theme_bw() +
scale_x_discrete(drop = FALSE) +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
panel.grid = element_blank()) + scale_x_discrete(drop = FALSE)
`summarise()` has grouped output by 'orig.ident'. You can override using the `.groups` argument.Scale for x is already present.
Adding another scale for x, which will replace the existing scale.
ggpubr::ggarrange(
p1,
p2,
align = "v",
heights = c(1, 1),
ncol = 1,
nrow = 2
)

Concordance of genotype and van Galen celltype annotation in AML
samples. NB: Cells with only wt transcript detected (“healthy”) in AML
samples might not be actually healthy (heterozygosity, clonality).
galen_aml_donors_metadata <- galen_aml_donors@meta.data
table(
filter(galen_aml_donors_metadata, SampleClass != "HealthyDonor")$genotype,
filter(galen_aml_donors_metadata, SampleClass != "HealthyDonor")$CellType
)
HSC Prog earlyEry lateEry GMP ProMono Mono cDC pDC ProB B Plasma T CTL NK HSC-like Prog-like GMP-like ProMono-like Mono-like cDC-like
healthy 25 98 82 189 48 101 164 85 12 6 24 129 179 106 198 47 218 118 195 180 98
malignant 5 11 15 6 9 8 2 9 4 1 5 2 5 5 10 39 188 114 292 129 80
not_detected 409 444 403 872 329 413 2025 474 118 98 378 946 6201 966 1604 1850 3290 1591 929 2241 1890
Sex bias in cells that can be used to train classifier (mut
transcript detected from AML donor, or from healthy donor).
p1 <- galen_aml_donors@meta.data %>%
filter(genotype == "malignant" |
SampleClass == "HealthyDonor") %>%
mutate(class = ifelse(genotype == "malignant", "malignant", "healthy")) %>%
group_by(Gender, class) %>%
summarise(n_cells = n()) %>%
ungroup() %>%
ggplot(aes(x = class, y = n_cells, fill = Gender)) +
geom_bar(position = "fill", stat = "identity") +
# geom_bar(stat = "identity") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
panel.grid = element_blank())
`summarise()` has grouped output by 'Gender'. You can override using the `.groups` argument.
p2 <- galen_aml_donors@meta.data %>%
filter(genotype == "malignant" |
SampleClass == "HealthyDonor") %>%
mutate(class = ifelse(genotype == "malignant", "malignant", "healthy")) %>%
group_by(Gender, class) %>%
summarise(n_cells = n()) %>%
ungroup() %>%
ggplot(aes(x = Gender, y = n_cells, fill = class)) +
geom_bar(position = "fill", stat = "identity") +
# geom_bar(stat = "identity") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
panel.grid = element_blank())
`summarise()` has grouped output by 'Gender'. You can override using the `.groups` argument.
p1 | p2

Accuracy of the classifier
Check the accuracy of the van Galen classifier based on concordance
with clinical blast count. This has already been checked in the paper
and the answer is that the correlation is high.
# samples with unclear prediction
galen_aml_donors@meta.data %>%
filter(PredictionRefined == "unclear") %>%
pull(orig.ident) %>%
unique() %>% as.character()
[1] "AML314.D0" "AML314.D31" "AML371.D0" "AML371.D34" "AML722B.D0" "AML722B.D49" "AML997.D0" "AML997.D35"
pred_malignant_vs_blast_count <- galen_aml_donors@meta.data %>%
group_by(
orig.ident,
PredictionRefined,
`Blast count`,
`Cell number`,
Sample,
Gender,
# DriverMutations,
`Days from diagnosis`
) %>%
summarize(PredictionRefinedProportion = n() / `Cell number`) %>%
filter(PredictionRefined == "malignant") %>%
select(
`Blast count`,
PredictionRefinedProportion,
orig.ident,
Sample,
Gender,
# DriverMutations,
`Days from diagnosis`
) %>%
unique() %>%
mutate(`Blast count` = as.numeric(`Blast count`)) %>%
ungroup()
Warning: Returning more (or less) than 1 row per `summarise()` group was deprecated in dplyr 1.1.0.
Please use `reframe()` instead.
When switching from `summarise()` to `reframe()`, remember that `reframe()` always returns an ungrouped data frame and adjust accordingly.`summarise()` has grouped output by 'orig.ident', 'PredictionRefined', 'Blast count', 'Cell number', 'Sample', 'Gender', 'Days from diagnosis'. You can override using the `.groups` argument.Adding missing grouping variables: `PredictionRefined`, `Cell number`
nrow(pred_malignant_vs_blast_count)
[1] 26
pred_malignant_vs_blast_count %>%
ggplot(aes(
x = PredictionRefinedProportion,
y = `Blast count`,
group = Sample,
color = Sample
)) +
geom_point() +
geom_abline(slope = 1, intercept = 0) +
ggrepel::geom_text_repel(aes(label = orig.ident),
size = 3,
max.overlaps = Inf) +
theme_bw() +
coord_equal() +
theme(panel.grid = element_blank())

cor.test(
pred_malignant_vs_blast_count$PredictionRefinedProportion,
pred_malignant_vs_blast_count$`Blast count`
)
Pearson's product-moment correlation
data: pred_malignant_vs_blast_count$PredictionRefinedProportion and pred_malignant_vs_blast_count$`Blast count`
t = 12.296, df = 24, p-value = 7.529e-12
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
0.8460991 0.9680065
sample estimates:
cor
0.9289867
If the sex bias in healthy and AML samples matters, then the
difference between predicted and observed blast count (error) should be
larger in females than males.
Predicted proportion of malignant cells agrees less with clinical
blast count for female samples. Markedly, female samples have an
overestimated proportion of malignant cell.
However, this could also be due to other factors covarying with Sex,
like mutated gene or blast count.
Median absolute error 10.8%(F), 3.1%(M).
pred_malignant_vs_blast_count_error <- pred_malignant_vs_blast_count %>%
mutate(error = PredictionRefinedProportion - `Blast count`,
absolute_error = abs(error),
relative_error = PredictionRefinedProportion / `Blast count`) %>%
pivot_longer(
cols = c(error, absolute_error, relative_error),
names_to = "error_type",
values_to = "error"
) %>%
group_by(error_type, Gender)
pred_malignant_vs_blast_count_error %>%
summarize(median_error = round(median(error), 3))
`summarise()` has grouped output by 'error_type'. You can override using the `.groups` argument.
symmetric_limits <- function (x)
{
max <- max(abs(x))
c(-max, max)
}
p1 <- pred_malignant_vs_blast_count_error %>%
filter(error_type %in% c("error", "absolute_error")) %>%
ggplot(aes(x = error_type, y = error * 100, fill = Gender)) +
geom_hline(yintercept = 0, linetype = 2) +
geom_boxplot(outlier.size = 0) +
geom_point(pch = 21, position = position_jitterdodge(jitter.width = 0.1)) +
scale_y_continuous(labels = scales::percent_format(scale = 1)) +
theme_bw() +
theme(panel.grid = element_blank(), legend.position = "None") +
ylab("Prediction error % blasts") +
scale_y_continuous(limits = symmetric_limits) +
xlab(element_blank()) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
Scale for y is already present.
Adding another scale for y, which will replace the existing scale.
p2 <- pred_malignant_vs_blast_count_error %>%
filter(error_type %in% c("relative_error")) %>%
ggplot(aes(x = error_type, y = log2(error), fill = Gender)) +
geom_hline(yintercept = 0, linetype = 2) +
geom_boxplot(outlier.size = 0) +
geom_point(pch = 21, position = position_jitterdodge(jitter.width = 0.1)) +
theme_bw() +
theme(panel.grid = element_blank()) +
ylab("log2 relative error % blasts") +
scale_y_continuous(limits = symmetric_limits) +
xlab(element_blank()) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
ggpubr::ggarrange(p1, p2, align = "h", widths = c(1, 1.2))

Data quality
According to manuscript, cells were filtered for >1000 UMI,
>500 genes and <20% mitochondrial+ribosomal transcripts.
The Rdata object contains filtered cells.
VlnPlot(galen_aml_donors, features = c("nCount_RNA", "nFeature_RNA"), ncol = 1, log = T, pt.size = 0)

Low dimensional embedding
AML and healthy donors
Normalize data by library size and log transform.
Identify 2000 highly variable genes.
Scale/z-score highly variable genes.
galen_aml_donors <- NormalizeData(galen_aml_donors, scale.factor = 10000)
galen_aml_donors <- FindVariableFeatures(galen_aml_donors)
galen_aml_donors <- ScaleData(galen_aml_donors)
Calculate PCA and check elbow plot for appropriate number of PCs for
UMAP embedding.
galen_aml_donors <- RunPCA(galen_aml_donors)
ElbowPlot(galen_aml_donors, ndims = 50) +
coord_cartesian(ylim = c(0, NA))

Calculate UMAP embedding.
galen_aml_donors <- RunUMAP(galen_aml_donors, dims = 1:15)
Check UMAP for technical and biological variation.
Higher read counts in erythrocyte progenitors.
# technical
FeaturePlot(galen_aml_donors, features = "nCount_RNA", pt.size = 0.005, max.cutoff = "q99") + coord_equal()

FeaturePlot(galen_aml_donors, features = "nFeature_RNA", pt.size = 0.005) + coord_equal()

# annotation biological
DimPlot(galen_aml_donors, group.by = "SampleClass", shuffle = T, pt.size = 0.005) + coord_equal()

DimPlot(galen_aml_donors, group.by = "IsCancerCelltype", shuffle = T, pt.size = 0.005) + coord_equal()

DimPlot(galen_aml_donors, group.by = "PredictionRefined", shuffle = T, pt.size = 0.005) + coord_equal()

DimPlot(galen_aml_donors, group.by = "CellType", shuffle = T, pt.size = 0.005, label = T, label.size = 3) + coord_equal()

DimPlot(galen_aml_donors, group.by = "orig.ident", shuffle = T, pt.size = 0.005, label = T, label.size = 2) + coord_equal() + NoLegend()

FeaturePlot(galen_aml_donors, features = "Age", pt.size = 0.005) + coord_equal()

DimPlot(subset(galen_aml_donors, SampleClass == "HealthyDonor"), group.by = "orig.ident", shuffle = T, pt.size = 0.005) + coord_equal()

# sex differences
DimPlot(galen_aml_donors, group.by = "Gender", shuffle = T, pt.size = 0.005) + coord_equal()

DimPlot(subset(galen_aml_donors, SampleClass == "AmlDonor"), group.by = "Gender", shuffle = T, pt.size = 0.005) + coord_equal()

DimPlot(subset(galen_aml_donors, SampleClass == "AmlDonor"), group.by = "Sample", shuffle = T, pt.size = 0.005, split.by = "Gender") + coord_equal()

DimPlot(galen_aml_donors, group.by = "Sample", shuffle = T, pt.size = 0.005, split.by = "Gender") + coord_equal()

Embedding healthy donors
Create an embedding of just the healthy samples to visualize the
normal hematopoietic trajectory.
Same procedure as above.
galen_aml_donors_healthy <- subset(galen_aml_donors, SampleClass == "HealthyDonor")
galen_aml_donors_healthy <- FindVariableFeatures(galen_aml_donors_healthy)
galen_aml_donors_healthy <- ScaleData(galen_aml_donors_healthy)
galen_aml_donors_healthy <- RunPCA(galen_aml_donors_healthy)
ElbowPlot(galen_aml_donors_healthy, ndims = 50) +
coord_cartesian(ylim = c(0, NA))

galen_aml_donors_healthy <- RunUMAP(galen_aml_donors_healthy, dims = 1:15)
DimPlot(galen_aml_donors_healthy, group.by = "CellType", shuffle = T, pt.size = 0.01, label = T, label.size = 3) +
coord_equal()

DimPlot(galen_aml_donors_healthy, group.by = "orig.ident", shuffle = T, pt.size = 0.01, label = T, label.size = 3) +
coord_equal() + NoLegend()

AML and healthy donors excluding sex-specific genes
SDE scores for all protein-coding genes in 45 tissues common to men
and women. Genes were analyzed by NOISeqBIO with scores of zero given
for genes with insignificant differential expression. Other genes have
SDE scores below zero for men-biased expression and above zero for
women-biased expression. (CSV 2205 kb)
Bone marrow is not available. Instead use whole blood and spleen.
sex_genes <- read_csv("gershoni_et_al/12915_2017_352_MOESM3_ESM.csv")
Rows: 18759 Columns: 46── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): Gene
dbl (45): Adipose-Subcutaneous, Adipose-Visceral, Adrenal_Gland, Artery-Aorta, Artery-Coronary, Artery-Tibial, Bladder, Brain-Amygdala, Brain-Anterior_cingulate_cortex, Brain-Caudate, Brain-...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
sex_genes <- sex_genes %>%
select(Gene, Spleen, Whole_Blood) %>%
filter(Spleen != 0 | Whole_Blood != 0) %>%
mutate(gene = gsub("_.*", "", Gene)) %>%
pull(gene)
sex_genes <- c(sex_genes, "XIST")
# make sure IDs match
sex_genes[!sex_genes %in% rownames(galen_aml_donors)]
character(0)
length(sex_genes)
[1] 28
Also exclude genes expressed on Y chromosome. 149 genes present in
scRNA-seq data.
y_chrom_genes <- read_lines("reference_sources/gencode.v44_gene_names_chrY.txt")
y_chrom_genes <- y_chrom_genes[y_chrom_genes %in% rownames(galen_aml_donors)]
length(y_chrom_genes)
[1] 149
sex_genes <- unique(c(sex_genes, y_chrom_genes))
6 out of 2000 highly variable genes (AML+healthy) have sex-specific
expression.
I don’t think removal of all genes that are differentially expressed
betwen M and F should be remove, because there variables that co-vary
with sex.
# sex-specific genes used for embedding
sex_genes[sex_genes %in% VariableFeatures(galen_aml_donors)]
[1] "EIF1AY" "MAP7D2" "VEGFA" "XIST" "SHOX" "ASMT"
Re-calculate PCA, excluding sex-specific genes from HVG list.
galen_aml_donors <-
RunPCA(
galen_aml_donors,
features = VariableFeatures(galen_aml_donors)[!VariableFeatures(galen_aml_donors) %in% sex_genes],
reduction.name = "pca_no_sex"
)
ElbowPlot(galen_aml_donors, ndims = 50, reduction = "pca_no_sex") +
coord_cartesian(ylim = c(0, NA))

galen_aml_donors <-
RunUMAP(
galen_aml_donors,
dims = 1:15,
reduction = "pca_no_sex",
reduction.name = "umap_no_sex"
)
I would check if the integration of sexes has improved if
CalcAlignmentMetric was still available in Seurat v5…
MixingMetric has replaced CalcAlignmentMetric (Seurat v2).
Explanation of MixingMetric
- Look at the k.max nearest neighbors for a cell across all
datasets.
- Compute the k closest neighbors for each dataset individually for
the same cell.
- Find the rank in the overall neighborhood list (from 1.) that
corresponds to the kth neighbor in each dataset (max of k.max) and take
the median across all datasets.
- Compute 1-3 for every cell and average. This average is the value
that is returned by the MixingMetric function. However, it’s usually
more intuitive to think of high values of “mixing” to be better so for
visualization, we often plot max.k - MixingMetric.
Deafult max.k = 300.
According to this metric, mixing of cells from male and female AML
donors has slightly improved.
mixing_aml_sexes <- MixingMetric(
subset(galen_aml_donors, SampleClass == "AmlDonor"),
reduction = "pca",
dims = 1:15,
grouping.var = "Gender"
)
mixing_aml_sexes_corrected <- MixingMetric(
subset(galen_aml_donors, SampleClass == "AmlDonor"),
reduction = "pca_no_sex",
dims = 1:15,
grouping.var = "Gender"
)
300 - mean(mixing_aml_sexes)
[1] 253.2416
300 - mean(mixing_aml_sexes_corrected)
[1] 254.2387
# sex differences
DimPlot(
subset(galen_aml_donors, SampleClass == "AmlDonor"),
reduction = "umap_no_sex",
group.by = "Gender",
shuffle = T,
pt.size = 0.005
) + coord_equal()

Task 2: ML classifier malignant cells
Task Description
An interesting feature of the dataset is the availability of labels
distinguishing malignant and non-malignant single cells. The second task
is to exploit these labels to build a suitable ML classifier that
predicts the normal or malignant identity of single cells. Please detail
on how your classifier works, whether it is interpretable, its
performance, and the train-test split you use for your machine learning
experiment.
Considerations for the task
The tricky part is not training a classifier, but to consider biology
and technology to train a good classifier.
I assume that “labels distinguishing malignant and non-malignant
single cells” in the task description refers to the genotyped cells, and
not the labels predicted by van Galen et al. based on their random
forest classifiers.
Van Galen et al. train two random forest classifiers (see below), in
order to classify cells from AML donors into malignant and normal
cells.
The first classifier is for HSPC cell types and the second for HSPC cell
types + malignant vs. normal. Each classifier consists of an “outer”
classifier for feature selection and an “inner” classifier that is used
for prediction. Due to very limited number of genotyped cells that can
be used for training, they do not hold out a test set. Instead, to
evaluate their model performance, they perform 5-fold cross validation
of the “inner” classifier.
Finally, they use the model trained on all available data for
classification of AML cells that were not genotyped.
I assume that at the time there was no independent dataset available
with genotyped AML cells to test for generalizability of the model to
other scRNA-seq technologies (droplet-based, 5’ sequencing), human
populations, or driver mutations.
Points of critique
- Including all training data in the feature selection makes the CV
results of the “inner” classifier less informative.
- The CV is supposed to evaluate over- and underfitting. To estimate
overfitting (complexity of the model), the model should be evaluated on
how well it extrapolates to another donor and to another driver
mutation, not a random sample of all training cells.
- All healthy cells in the training data are male. Van Galen do not
address this by removing sex-specific genes from the features. (I’m
honestly suprised that the classifier doesn’t perform even worse on
female samples based on blast count than it does. A male sample with
loss of y might play a role, and expression level/sparsity of
sex-specific genes.)
- Cells were genotyped based on transcripts of following genes:
TET2, SETD2, PTPN11, NRAS, SF3A1, BCOR, KIT, RAD21, TP53, BRCC3, RUNX1,
FLT3, IDH2, DNMT3A, SMC3, NPM1, KRAS.
Cells with higher expression of these genes are more likely to be
genotyped and make it into the training data. Their expression should be
excluded from the feature list, also to make it more generalisable to
other driver mutations.
- The classifier does not consider possible differences in
transcription between non-malignant HSPCs from AML donors and healthy
donors. Non-malignant cells from AML donors have possibly altered
transcription due to changes in the environment in the bone
marrow.
The classifier only considers healthy cells from healthy donors, and not
non-malignant cells from AML donors. This is a limitation of the
technology, because cells with wt transcript detected but no mutated
transcript detected cannot confidently be classified as normal, due to
clonality and heterozygosity.
However, there might be a subset of cells from donors with known
zygosity and clonal structure where this is possible.
What I agree with, is the observation that, generally, malignant HSCs
are transcriptionally more similar to healthy HSCs than to malignant
monocyte progenitors. Therefore, it makes sense to guide the model with
information on similarity towards healthy HSPC cell types.
Random forest classifiers allow the extraction of feature importance
metrics (e.g. number of trees in which a feature is used). However, one
has to differ between a minimal optimal subset and an all
relevant subset of genes. This is especially relevant for
gene-expression based classifiers due to redundance (think gene
modules/signatures). See Kursa
et al. 2014 for more details.
Van Galen et al. select as features the genes chosen most often in
the outer classifier, across 1000 trees. This does not seem to consider
classes.
Ideas
- Instead of predicting HSC and HSC-like cells, why not give a
similarity score towards each healthy cell type as input feature. This
would allow to inform the classifier for relevant features like
combinations of signatures (e.g. LSCs arising from GMPs with mixed
HSC/GMP signature).
- Why include lymphoid and erythroid cell types in the malignant
vs. non-malignant classifier? They are transcriptionally very distinct
and in AML they are not malignant. The classifier might have an easier
time if they are excluded.
- Consider other methods to assign the most similar healthy cell type,
such as label transfer via projection onto the healthy reference
(Azimuth).
- Use an independent test set of genotypes AML cells.
Van Galen classifier malignant vs. normal AML
cells
Transcriptional heterogeneity of malignant and healthy cells
The goal is to develop a classifier of malignant vs. healthy HSPCs
and myeloid cells from AML patients, that is generalizable to other AML
types with other driver mutations and other scRNA-seq
technologies/datasets.
Evaluate the transcriptional heterogeneity of AML subtypes and
malignant vs. healthy cells.
Q1: Do healthy cells differ from malignant cells based on their
transcriptome?
Q2: Is the malignant transcriptional profile specific to driver
mutations?
We are only interested in myeloid and HSPC cells here, because
distinction of erythroid and lymphoid cells is relatively easy. Those
cells are not malignant in acute myeloid leukemia.
For a start, use HSPCs + myeloid cells, according to van Galen cell
type annotation.
Include healthy donors, to see if the cells genotyped as wt group
with definitive healthy cells.
selected_samples <- donor_metadata %>%
# filter(
# !grepl("BM", Sample),
# # `Blast count` > 0.1,
# # `Days from diagnosis` == "D0"
# ) %>%
pull(SampleId)
galen_aml_donors_myeloid_hspc <-
subset(
galen_aml_donors,
subset = (orig.ident %in% selected_samples & SampleClass == "HealthyDonor" |
((CellTypeGeneral == "Myeloid/HSPC-like" |
CellTypeGeneral == "Myeloid/HSPC") & genotype != "not_detected"))
)
table(galen_aml_donors_myeloid_hspc$genotype)
healthy malignant not_detected
1734 886 6558
# only keep SampleId-genotype combinations with at least 10 cells
samples_enough_cells <- names(which(table(galen_aml_donors_myeloid_hspc$orig.ident) >= 10))
galen_aml_donors_myeloid_hspc <-
subset(
galen_aml_donors_myeloid_hspc,
subset = orig.ident %in% samples_enough_cells
)
galen_aml_donors_myeloid_hspc@meta.data <- galen_aml_donors_myeloid_hspc@meta.data %>%
mutate(
SampleId_genotype =
paste(
galen_aml_donors_myeloid_hspc$orig.ident,
galen_aml_donors_myeloid_hspc$genotype,
sep = "_"
)
) %>%
mutate(SampleId_genotype = ifelse(SampleClass == "HealthyDonor", gsub("not_detected", "healthy", SampleId_genotype), SampleId_genotype))
pb_counts <-
Seurat::AggregateExpression(
galen_aml_donors_myeloid_hspc,
assays = "RNA",
slot = "counts",
# group.by = "SampleId_CellTypeGeneral"
group.by = "SampleId_genotype"
# group.by = "orig.ident"
)$RNA
Names of identity class contain underscores ('_'), replacing with dashes ('-')
group_anno <- colnames(pb_counts)
group_anno <- gsub(".*-", "", group_anno)
pb_dge <- edgeR::DGEList(
counts = pb_counts,
samples = colnames(pb_counts),
group = group_anno
)
Filter out samples with low read count (=few filtered cells).
Keep samples with read counts > 50,000, which is all samples.
summary(pb_dge$samples$lib.size)
Min. 1st Qu. Median Mean 3rd Qu. Max.
3670 47074 195297 923075 618775 15170692
keep.samples <- pb_dge$samples$lib.size > 5e4
table(keep.samples)
keep.samples
FALSE TRUE
10 27
# pb_dge <- pb_dge[, keep.samples]
Filter out lowly expressed genes.
Keep genes with at least 10 CPM in at least 2 samples.
# genes with at least 1 CPM in at least 2 samples
keep.genes <- rowSums(edgeR::cpm(pb_dge) >=10) >= 2
table(keep.genes)
keep.genes
FALSE TRUE
15257 12642
pb_dge <- pb_dge[keep.genes, , keep=FALSE]
TMM normalization is performed to estimate effective library
size.
pb_dge <- edgeR::calcNormFactors(pb_dge, method = "TMM")
Calculate Multidimensional scaling (MDS) plot of distances between
gene expression profiles.
pb_mds <- limma::plotMDS(pb_dge, plot = F)
pb_mds_df <- data.frame(
list(
x = pb_mds$x,
y = pb_mds$y,
lib.size = pb_dge$samples$lib.size,
sample = gsub("-.*", "", colnames(pb_dge)),
group = pb_dge$samples$group
)
) %>%
merge(donor_metadata, by.x = "sample", by.y = "SampleId", all.x = T)
Check that major source of variation is not technical (sample size)
or irrelevant biological information in this case (blast count).
xlab_text <- paste0(
"Leading logFC dim 1 (",
round(pb_mds$var.explained[1] * 100, 1),
"%)"
)
ylab_text <- paste0(
"Leading logFC dim 2 (",
round(pb_mds$var.explained[2] * 100, 1),
"%)"
)
mds_theme <- theme_bw() +
theme(panel.grid = element_blank())
pb_mds_df %>%
unique() %>%
ggplot(aes(x, y, label = sample)) +
geom_point(aes(color = log(lib.size))) +
# ggrepel::geom_text_repel() +
xlab(xlab_text) + ylab(ylab_text) +
mds_theme

pb_mds_df %>%
ggplot(aes(x, y, label = sample)) +
geom_point(aes(color = `Blast count`)) +
# ggrepel::geom_text_repel() +
xlab(xlab_text) + ylab(ylab_text) +
mds_theme

pb_mds_df %>%
ggplot(aes(x, y, label = sample)) +
geom_point(aes(color = `Days from diagnosis` == "D0")) +
# ggrepel::geom_text_repel() +
xlab(xlab_text) + ylab(ylab_text) +
mds_theme

pb_mds_df %>%
ggplot(aes(x, y, label = sample)) +
geom_point(aes(color = group)) +
# ggrepel::geom_text_repel() +
xlab(xlab_text) + ylab(ylab_text) +
mds_theme

pb_mds_df %>%
ggplot(aes(x, y, label = sample)) +
geom_point(aes(color = Gender)) +
# ggrepel::geom_text_repel() +
xlab(xlab_text) + ylab(ylab_text) +
mds_theme

pb_mds_df %>%
ggplot(aes(x, y, label = sample)) +
geom_point(aes(color = Sample)) +
ggrepel::geom_text_repel(
size = 2,
min.segment.length = 0,
max.overlaps = Inf
) +
xlab(xlab_text) + ylab(ylab_text) +
mds_theme +
theme(legend.position = "None")

Check if the samples split by mutation.
sampleId_mutation_list <- donor_metadata %>%
select(SampleId, `RHP Mutations`, `Common translocation`) %>%
filter(SampleId %in% pb_mds_df$sample) %>%
pivot_longer(cols = c(`RHP Mutations`, `Common translocation`), values_to = "driver_mutation") %>%
select(-name) %>%
filter(!driver_mutation %in% c("Not performed", "None Detected", "Unknown", "NA") & !is.na(driver_mutation)) %>%
separate_longer_delim(cols = driver_mutation, "/// ") %>%
mutate(driver_mutation = gsub(" .*", "", driver_mutation)) %>%
# unique for samples with multiple mutations in same gene
unique() %>%
mutate(value = T) %>%
pivot_wider(id_cols = SampleId, names_from = driver_mutation, values_fill = F) %>%
pivot_longer(cols = -SampleId, names_to = "driver_mutation")
pb_mds_df_driver_mutation <- merge(pb_mds_df, sampleId_mutation_list, by.x = "sample", by.y = "SampleId", all.x = T)
PC1 aligns with DNMTA3. Indication that DNMT3 has a distinct
transcriptional profile.
“DNMT3A mutations occur in approximately 20% of AML cases and are
associated with changes in DNA methylation. CDKN2B plays an important
role in the regulation of hematopoietic progenitor cells and DNMT3A
mutation is associated with CDKN2B promoter methylation.” https://pmc.ncbi.nlm.nih.gov/articles/PMC7106122/
KRAS, FLT3-ITD, NPM1, NRAS
pb_mds_df_driver_mutation %>%
filter(group == "malignant") %>%
ggplot(aes(x, y, label = sample)) +
geom_point(aes(color = value)) +
facet_wrap(~driver_mutation) +
xlab(xlab_text) + ylab(ylab_text) +
theme_bw() +
theme(panel.grid = element_blank()) +
coord_equal()

unique(sampleId_mutation_list$driver_mutation)
[1] "KRAS" "NRAS" "NOTCH2" "SF3A1" "CBFB-MYH11" "DNMT3A" "NPM1" "TET2" "FLT3-ITD"
[10] "CEBPA" "FLT3" "JAK3" "TP53" "RUNX1" "SETD2" "BCOR" "BCORL1" "NOTCH1"
[19] "SMC3" "ATM" "BRCC3" "KIT" "RAD21" "RUNX1-RUNX1T1"
genes_genotypes <-
c(
"NRAS",
"KRAS",
"SF3A1",
"NOTCH1",
"NOTCH2",
"SF3A1",
"CBFB",
"MYH11",
"NPM1",
"JAK3",
"DNMT3A",
"FLT3",
"TP53",
"SETD2",
"RUNX1",
"BCOR",
"BCORL1",
"PTPN11",
"SMC3",
"IDH2",
"TET2",
"BRCC3",
"KIT",
"RAD21",
"MLL",
"CEBPA"
)
Azimuth projection
Alternative to cell type identification with a random forest model is
label transfer from a reference atlas.
Perform label transfer from the Azimuth bone marrow reference atlas.
Instead of training a random forest classifier to determine the most
similar healthy equivalent of a cell.
Azimuth provides cell type labels at 2 levels, see interactive
reference.
For this step, we are not interested in the genes distinguishing the
different cell types because they are already well defined. So we don’t
need a RF classifier where we can extract feature importance
metrics.
The label transfer is very fast and easy and provides a well
established reference framework of cell type classifications and
hierarchies.
Reference downloaded from here.
library(Azimuth)
Registered S3 methods overwritten by 'dbplyr':
method from
print.tbl_lazy
print.tbl_sql
Registered S3 method overwritten by 'SeuratDisk':
method from
as.sparse.H5Group Seurat
Attaching shinyBS
library(SeuratData)
# The RunAzimuth function can take a Seurat object as input
# overwrites miscellanous data, so saving as new object
galen_aml_donors_azimuth <- RunAzimuth(galen_aml_donors,
reference = "azimuth_reference/bonemarrowref.SeuratData/inst/azimuth/")
Add Azimuth labels to original seurat object.
galen_aml_donors <-
AddMetaData(
galen_aml_donors,
galen_aml_donors_azimuth@meta.data,
col.name = c("predicted.celltype.l1", "predicted.celltype.l2")
)
The function returns cell type labels on 2 levels, and 2 QC metrics,
a mapping score and a prediction score for each level.
It also returns the projection of the cells onto the reference UMAP
space.
DimPlot(galen_aml_donors_azimuth, group.by = "predicted.celltype.l1", shuffle = T, label = T) + coord_equal()
FeaturePlot(galen_aml_donors_azimuth, features = "predicted.celltype.l1.score") + coord_equal()
DimPlot(galen_aml_donors_azimuth, group.by = "predicted.celltype.l2", shuffle = T, label = T) + coord_equal()
FeaturePlot(galen_aml_donors_azimuth, features = "predicted.celltype.l2.score") + coord_equal()
FeaturePlot(galen_aml_donors_azimuth, features = "mapping.score") + coord_equal()
DimPlot(galen_aml_donors_azimuth, group.by = "SampleClass", shuffle = T) + coord_equal()
DimPlot(galen_aml_donors_azimuth, group.by = "CellType", shuffle = T, label = T) + coord_equal()
Compare Azimuth annotations with backspin cluster annotation,
followed by RF. Also compare with malignant/non malignant RF
annotation.
heatmap(table(galen_aml_donors$backspin_celltype, galen_aml_donors$predicted.celltype.l1), scale = NULL, Rowv = NA, Colv = NA)
heatmap(table(galen_aml_donors$backspin_celltype, galen_aml_donors$predicted.celltype.l2), scale = NULL, Rowv = NA, Colv = NA)
heatmap(table(galen_aml_donors$CellType, galen_aml_donors$predicted.celltype.l1), scale = NULL, Rowv = NA, Colv = NA)
heatmap(table(galen_aml_donors$CellType, galen_aml_donors$predicted.celltype.l2), scale = NULL, Rowv = NA, Colv = NA)
In the end, I realized that the Azimuth annotations are problematic,
becaue either very granular or useless because lumping everything into
HSPC.
Train classifier
backspin celltype annotation is available for 783 cells of 1,590 BM5
CD34+CD38–.
y <- factor(galen_aml_donors_healthy$backspin_celltype)
X <- galen_aml_donors_healthy@assays$RNA@layers$data
X <- X[,!is.na(y)]
rownames(X) <- rownames(galen_aml_donors_healthy)
y <- y[!is.na(y)]
# # combine HSC and Prog
# y[y %in% c("HSC", "Prog")] <- "HSC_Prog_"
Van Galen et al. select genes with average normalized expression
>= 0.01 (normalized to 10k reads). This is biased by class. Instead,
select genes expressed in 5% cells in at least 1 class (10141
genes).
# mean normalized expression >= 0.01
keep.genes <- rowMeans(exp(X) - 1) >= 0.01
table(keep.genes)
keep.genes
FALSE TRUE
13825 14074
# expressed in 1% cells
keep.genes.expressed <- rowSums((X > 0) / ncol(galen_aml_donors_healthy)) >= 0.01
table(keep.genes.expressed)
keep.genes.expressed
FALSE TRUE
16740 11159
# expressed in 1% cells in at least 1 class
classes <- unique(y)
keep.genes.class <- lapply(classes, FUN = function(class) {
genes <- names(which(rowSums((X[,y == class] > 0) / sum(y == class)) >= 0.05))
return(genes)
})
keep.genes.class <- rownames(X) %in% unlist(keep.genes.class)
table(keep.genes.class)
keep.genes.class
FALSE TRUE
17758 10141
X <- X[which(keep.genes.class),]
All cells are male. Remove sex-specific genes anyways.
keep.genes <- !rownames(X) %in% sex_genes
table(keep.genes)
keep.genes
FALSE TRUE
38 10103
X <- X[which(keep.genes),]
sampsize is the number of cells sampled from each class. By default
ceiling(.632*nrow(x)). This is one way to deal with
imbalanced data in RF by using balanced data sets for each tree, even if
the data is not balanced, which is made possible by bootstrap.
Train 100 trees because I’m impatient.
set.seed(123)
rf.celltype.outer <- randomForest(
x = t(X),
y = y,
sampsize = rep(50, length(unique(y))),
ntree = 200,
do.trace = F
)
rf.celltype.outer
Call:
randomForest(x = t(X), y = y, ntree = 200, sampsize = rep(50, length(unique(y))), do.trace = F)
Type of random forest: classification
Number of trees: 200
No. of variables tried at each split: 100
OOB estimate of error rate: 16.51%
Confusion matrix:
B cDC CTL earlyEry GMP HSC lateEry Mono NK pDC Plasma ProB Prog ProMono T class.error
B 109 0 1 0 0 0 0 2 0 0 0 0 0 0 1 0.03539823
cDC 2 240 2 1 2 1 0 16 2 6 0 0 0 9 0 0.14590747
CTL 1 0 199 1 0 0 0 0 12 0 0 1 0 0 47 0.23754789
earlyEry 0 0 1 407 6 29 94 0 0 0 1 11 67 1 2 0.34248788
GMP 1 4 2 2 455 6 0 1 0 0 1 2 38 19 1 0.14473684
HSC 2 0 4 3 1 1143 0 0 0 0 0 9 107 0 1 0.10000000
lateEry 0 0 0 2 0 1 257 0 0 0 0 0 1 1 0 0.01908397
Mono 1 6 1 0 0 0 2 540 0 0 0 0 0 17 0 0.04761905
NK 0 0 6 0 0 0 0 1 147 0 0 0 1 0 2 0.06369427
pDC 1 0 0 0 0 0 0 0 0 82 0 1 7 0 0 0.09890110
Plasma 0 0 0 0 0 0 0 0 0 1 68 0 0 0 0 0.01449275
ProB 2 0 0 0 0 2 0 0 0 1 0 184 3 0 1 0.04663212
Prog 0 0 0 15 68 307 0 0 1 1 0 12 751 0 1 0.35034602
ProMono 0 4 2 1 21 0 1 78 0 0 0 0 0 516 1 0.17307692
T 1 1 35 1 0 0 0 1 5 1 0 0 0 0 675 0.06250000
Select 1k features from 14074 genes.
bestvar is the variable used to split the node (0 if node is
terminal).
rf.celltype.outer.used1k <-
names(sort(table(
rownames(rf.celltype.outer$importance)[rf.celltype.outer$forest$bestvar[rf.celltype.outer$forest$bestvar !=
0]]
), decreasing = TRUE)[1:1000])
plot(rf.celltype.outer$importance[rf.celltype.outer.used1k, 1]) # correlates to importance measure

set.seed(123)
rf.celltype.inner <- randomForest(
x = t(X[rf.celltype.outer.used1k,]),
y = y,
sampsize = rep(50, length(unique(y))),
ntree = 200,
do.trace = F
)
rf.celltype.inner
Call:
randomForest(x = t(X[rf.celltype.outer.used1k, ]), y = y, ntree = 200, sampsize = rep(50, length(unique(y))), do.trace = F)
Type of random forest: classification
Number of trees: 200
No. of variables tried at each split: 31
OOB estimate of error rate: 13.49%
Confusion matrix:
B cDC CTL earlyEry GMP HSC lateEry Mono NK pDC Plasma ProB Prog ProMono T class.error
B 106 0 1 0 0 0 0 2 0 0 0 0 0 0 4 0.06194690
cDC 3 249 0 1 2 1 0 3 0 6 0 1 2 13 0 0.11387900
CTL 1 0 220 0 0 0 0 0 12 0 1 0 0 0 27 0.15708812
earlyEry 2 1 2 435 13 18 71 0 0 0 1 11 63 0 2 0.29725363
GMP 1 2 1 3 456 4 0 0 0 3 1 1 32 27 1 0.14285714
HSC 1 0 0 7 2 1142 0 0 0 1 0 16 100 0 1 0.10078740
lateEry 0 0 0 7 0 0 255 0 0 0 0 0 0 0 0 0.02671756
Mono 1 9 0 0 0 0 2 542 0 0 0 0 0 13 0 0.04409171
NK 0 0 2 0 0 0 0 0 154 0 0 0 0 0 1 0.01910828
pDC 0 1 0 0 0 1 0 0 0 86 0 1 2 0 0 0.05494505
Plasma 0 0 0 0 0 0 0 0 0 1 68 0 0 0 0 0.01449275
ProB 0 0 0 0 0 3 0 0 0 1 0 188 0 0 1 0.02590674
Prog 0 0 0 8 47 206 0 0 2 3 0 14 876 0 0 0.24221453
ProMono 1 5 1 3 16 0 0 71 0 0 1 0 0 525 1 0.15865385
T 1 1 33 0 0 0 0 0 4 1 0 0 0 0 680 0.05555556
y_pred_healthy <- predict(rf.celltype.inner, newdata = t(X), type = "prob")
rf.celltype.inner$confusion
B cDC CTL earlyEry GMP HSC lateEry Mono NK pDC Plasma ProB Prog ProMono T class.error
B 106 0 1 0 0 0 0 2 0 0 0 0 0 0 4 0.06194690
cDC 3 249 0 1 2 1 0 3 0 6 0 1 2 13 0 0.11387900
CTL 1 0 220 0 0 0 0 0 12 0 1 0 0 0 27 0.15708812
earlyEry 2 1 2 435 13 18 71 0 0 0 1 11 63 0 2 0.29725363
GMP 1 2 1 3 456 4 0 0 0 3 1 1 32 27 1 0.14285714
HSC 1 0 0 7 2 1142 0 0 0 1 0 16 100 0 1 0.10078740
lateEry 0 0 0 7 0 0 255 0 0 0 0 0 0 0 0 0.02671756
Mono 1 9 0 0 0 0 2 542 0 0 0 0 0 13 0 0.04409171
NK 0 0 2 0 0 0 0 0 154 0 0 0 0 0 1 0.01910828
pDC 0 1 0 0 0 1 0 0 0 86 0 1 2 0 0 0.05494505
Plasma 0 0 0 0 0 0 0 0 0 1 68 0 0 0 0 0.01449275
ProB 0 0 0 0 0 3 0 0 0 1 0 188 0 0 1 0.02590674
Prog 0 0 0 8 47 206 0 0 2 3 0 14 876 0 0 0.24221453
ProMono 1 5 1 3 16 0 0 71 0 0 1 0 0 525 1 0.15865385
T 1 1 33 0 0 0 0 0 4 1 0 0 0 0 680 0.05555556
earlyEry misclassified as lateEry and Prog. Prog misclassified as
HSC.
heatmap(rf.celltype.inner$confusion, Rowv = NA, Colv = NA, scale = NULL)

rf.celltype.inner$confusion %>%
as.data.frame() %>%
rownames_to_column("class") %>%
ggplot(aes(x = class, y = class.error)) +
geom_bar(stat = "identity")

Predict celltype of malignant AML cells.
“In four patients (AML314, AML371, AML722B and AML997), for which we
detected few mutant transcripts and few high quality cells, we could not
confidently assign malignant cells.”
I also exclude AML475 and AML420B because they also only have 7 and 6
cells genotyped as malignant (and upon first classification, those cells
were lymphoid cells).
galen_aml_donors_malignant <- subset(galen_aml_donors, subset = (genotype == "malignant" & SampleClass != "HealthyDonor"))
table(galen_aml_donors_malignant$Sample)
AML1012 AML210A AML328 AML329 AML371 AML419A AML420B AML475 AML556 AML707B AML722B AML916 AML921A AML997
15 45 34 92 6 146 6 7 377 40 1 21 143 6
selected_samples <-
donor_metadata$Sample[!donor_metadata$Sample %in% c("AML314", "AML371", "AML722B", "AML997", "AML475", "AML420B")]
galen_aml_donors_malignant <-
subset(galen_aml_donors_malignant, Sample %in% selected_samples)
X_malignant <- galen_aml_donors_malignant@assays$RNA@layers$data
rownames(X_malignant) <- rownames(galen_aml_donors_malignant)
X_malignant <- X_malignant[rf.celltype.outer.used1k,]
y_pred_malignant <- predict(rf.celltype.inner, newdata = t(X_malignant), type = "prob")
y_pred_max <- colnames(y_pred_malignant)[max.col(y_pred_malignant)]
There are cells classified as lymphocytes, despite detection of
mutated transcripts. Same for early erythrocytes, pDC.
table(y_pred_max)
y_pred_max
B cDC CTL earlyEry GMP HSC lateEry Mono NK pDC Plasma ProB Prog ProMono T
11 85 7 31 112 4 5 155 8 7 2 18 176 285 7
table(y_pred_max, galen_aml_donors_malignant$Sample)
y_pred_max AML1012 AML210A AML328 AML329 AML419A AML556 AML707B AML916 AML921A
B 0 0 0 2 2 0 1 0 6
cDC 1 8 1 5 9 1 0 1 59
CTL 0 0 0 0 0 5 0 1 1
earlyEry 2 5 10 3 7 0 2 2 0
GMP 4 4 1 51 8 3 21 0 20
HSC 0 0 2 0 0 0 1 0 1
lateEry 0 0 0 3 0 1 0 0 1
Mono 1 8 0 0 40 103 0 0 3
NK 0 0 2 0 0 2 1 0 3
pDC 0 0 0 1 1 0 0 0 5
Plasma 0 0 0 0 0 2 0 0 0
ProB 0 1 4 0 7 0 1 5 0
Prog 5 9 13 20 67 1 13 11 37
ProMono 2 10 0 6 4 257 0 0 6
T 0 0 1 1 1 2 0 1 1
t(round(t(
table(y_pred_max, galen_aml_donors_malignant$Sample)
) / as.vector(colSums(
table(y_pred_max, galen_aml_donors_malignant$Sample)
)), 2))
y_pred_max AML1012 AML210A AML328 AML329 AML419A AML556 AML707B AML916 AML921A
B 0.00 0.00 0.00 0.02 0.01 0.00 0.03 0.00 0.04
cDC 0.07 0.18 0.03 0.05 0.06 0.00 0.00 0.05 0.41
CTL 0.00 0.00 0.00 0.00 0.00 0.01 0.00 0.05 0.01
earlyEry 0.13 0.11 0.29 0.03 0.05 0.00 0.05 0.10 0.00
GMP 0.27 0.09 0.03 0.55 0.05 0.01 0.52 0.00 0.14
HSC 0.00 0.00 0.06 0.00 0.00 0.00 0.03 0.00 0.01
lateEry 0.00 0.00 0.00 0.03 0.00 0.00 0.00 0.00 0.01
Mono 0.07 0.18 0.00 0.00 0.27 0.27 0.00 0.00 0.02
NK 0.00 0.00 0.06 0.00 0.00 0.01 0.03 0.00 0.02
pDC 0.00 0.00 0.00 0.01 0.01 0.00 0.00 0.00 0.03
Plasma 0.00 0.00 0.00 0.00 0.00 0.01 0.00 0.00 0.00
ProB 0.00 0.02 0.12 0.00 0.05 0.00 0.03 0.24 0.00
Prog 0.33 0.20 0.38 0.22 0.46 0.00 0.32 0.52 0.26
ProMono 0.13 0.22 0.00 0.07 0.03 0.68 0.00 0.00 0.04
T 0.00 0.00 0.03 0.01 0.01 0.01 0.00 0.05 0.01
There are some rather unexpected HSPC cell type predictions: early
erythroid cells and pDC cells. While lymphoid cell types are pretty
surely a misclassification, early ery and pDC can actually be mutated in
AML, according to literature.
https://www.mdpi.com/2072-6694/14/14/3375 Acute myeloid
leukemia (AML) with ≥2% plasmacytoid dendritic cells (pDC) has been
recently described as AML with pDC differentiation (pDC-AML)
characterized by pDC expansion with frequent RUNX1 mutations.
AML921A has 8% cells predicted as pDC and RUNX1 NM_001754 c.167T>C
p.L56S (63.5%, VUS)
In combination with the possible signature combinations in AML cells,
I want to keep those predictions.
However, there are too few cells to use them as classes in a
classifier. Instead, I’ll include them as features, alongside genes, and
predict malignant vs. normal classes.
I’ll only train 1 classifier, instead of inner and outer. This way,
validation of correct prediction of malignant cells is more
truthful.
X_healthy <- galen_aml_donors_healthy@assays$RNA@layers$data
X_healthy <- X_healthy[,!is.na(galen_aml_donors_healthy$backspin_celltype)]
rownames(X_healthy) <- rownames(galen_aml_donors_healthy)
# combine celltype prediction with gene expression
X_healthy_celltype <- rbind(X_healthy, t(y_pred_healthy))
X_malignant <- galen_aml_donors_malignant@assays$RNA@layers$data
rownames(X_malignant) <- rownames(galen_aml_donors_malignant)
# combine celltype prediction with gene expression
X_malignant_celltype <- rbind(X_malignant, t(y_pred_malignant))
X_celltype <- cbind(X_healthy_celltype, X_malignant_celltype)
dim(X_celltype)
[1] 27914 7828
X <- cbind(X_healthy, X_malignant)
dim(X)
[1] 27899 7828
y = factor(c(rep("healthy", ncol(X_healthy)), rep("malignant", ncol(X_malignant))))
table(y)
y
healthy malignant
6915 913
Keep cells expressed in >= 1% in at least one of the classes.
keep.genes.expressed <- rowSums((X > 0) / ncol(X)) >= 0.01
table(keep.genes.expressed)
keep.genes.expressed
FALSE TRUE
16569 11330
# expressed in 1% cells in at least 1 class
classes <- unique(y)
keep.genes.class <- lapply(classes, FUN = function(class) {
genes <- names(which(rowSums((X[,y == class] > 0) / sum(y == class)) >= 0.01))
return(genes)
})
keep.genes.class <- rownames(X) %in% unlist(keep.genes.class)
names(keep.genes.class) <- rownames(X)
table(keep.genes.class)
keep.genes.class
FALSE TRUE
15572 12327
X <- X[names(which(keep.genes.class)),]
X_celltype <- X_celltype[c(names(which(keep.genes.class)), colnames(y_pred_malignant)), ]
X_celltype_driver <- X_celltype[!rownames(X_celltype) %in% genes_genotypes, ]
Train 1 classifier with celltype probabilities as features and one
without and one without genes with driver mutations.
set.seed(123)
rf.malignant <- randomForest(
x = t(X),
y = y,
sampsize = rep(200, length(unique(y))),
ntree = 200,
do.trace = F
)
set.seed(123)
rf.malignant.celltype <- randomForest(
x = t(X_celltype),
y = y,
sampsize = rep(200, length(unique(y))),
ntree = 200,
do.trace = F
)
set.seed(123)
rf.malignant.celltype.driver <- randomForest(
x = t(X_celltype_driver),
y = y,
sampsize = rep(200, length(unique(y))),
ntree = 200,
do.trace = F
)
Including cell type probabilities gives a
rf.malignant
Call:
randomForest(x = t(X), y = y, ntree = 200, sampsize = rep(200, length(unique(y))), do.trace = F)
Type of random forest: classification
Number of trees: 200
No. of variables tried at each split: 111
OOB estimate of error rate: 8.23%
Confusion matrix:
healthy malignant class.error
healthy 6385 530 0.07664497
malignant 114 799 0.12486309
rf.malignant.celltype
Call:
randomForest(x = t(X_celltype), y = y, ntree = 200, sampsize = rep(200, length(unique(y))), do.trace = F)
Type of random forest: classification
Number of trees: 200
No. of variables tried at each split: 111
OOB estimate of error rate: 7.78%
Confusion matrix:
healthy malignant class.error
healthy 6419 496 0.07172813
malignant 113 800 0.12376780
rf.malignant.celltype.driver
Call:
randomForest(x = t(X_celltype_driver), y = y, ntree = 200, sampsize = rep(200, length(unique(y))), do.trace = F)
Type of random forest: classification
Number of trees: 200
No. of variables tried at each split: 110
OOB estimate of error rate: 8.01%
Confusion matrix:
healthy malignant class.error
healthy 6383 532 0.0769342
malignant 95 818 0.1040526
y_malignant_celltype_pred <- predict(rf.malignant.celltype, newdata = t(X_celltype), type = "prob")
y_malignant_celltype_pred_max <- colnames(y_malignant_celltype_pred)[max.col(y_malignant_celltype_pred)]
donor_list <- unique(galen_aml_donors_malignant$Sample)
names(donor_list) <- donor_list
# donor_accuracy_rf.malignant.celltype <- lapply(donor_list, function(donor) sum(y_malignant_celltype_pred_max[donors == donor] == "malignant") / sum(donors == donor))
# donor_accuracy_rf.malignant.celltype
Model evaluation
Cross-validation on AML donors
Pick 4 donors at random (to save time).
Accuracy is how many cells are correctly classified as malignant.
With this cross validation, we could do more paramter testing. Like
the number of features to pick for the inner cell type classifier. Or
how many paramters to sample for each decision in the tree. Or the depth
of the trees.
donors <- c(galen_aml_donors_healthy$Sample, galen_aml_donors_malignant$Sample)
table(donors)
donors
AML1012 AML210A AML328 AML329 AML419A AML556 AML707B AML916 AML921A BM1
15 45 34 92 146 377 40 21 143 108
BM2 BM3 BM4 BM5 CD34+ BM5 CD34+CD38-
188 643 3738 1431 807
donors_cv <- unique(galen_aml_donors_malignant$Sample, galen_aml_donors_healthy$Sample)
set.seed(123)
donors_cv <- sample(donors_cv, size = 4)
donors_cv
[1] "AML419A" "AML329" "AML707B" "AML210A"
cv_results <- lapply(donors_cv, function(donor) {
cat(donor)
cat("\n")
# train and validation split
X_celltype_cv_train <- X_celltype[, donors != donor]
X_celltype_cv_test <- X_celltype[, donors == donor]
y_cv_train <- y[donors != donor]
y_cv_test <- y[donors == donor]
# train on test data
set.seed(123)
rf.malignant.celltype.cv <- randomForest(
x = t(X_celltype_cv_train),
y = y_cv_train,
sampsize = rep(200, length(unique(y_cv_train))),
ntree = 200,
do.trace = FALSE
)
# predict validation data
y_cv_pred <-
predict(
rf.malignant.celltype.cv,
newdata = t(X_celltype_cv_test),
type = "prob"
)
y_cv_pred_max <- colnames(y_cv_pred)[max.col(y_cv_pred)]
# calculate accuracy
accuracy <- sum(y_cv_pred_max == y_cv_test) / length(y_cv_pred_max)
return(accuracy)
})
AML419A
AML329
AML707B
AML210A
names(cv_results) <- donors_cv
Cross validation when leaving out driver mutation genes.
cv_results_driver <- lapply(donors_cv, function(donor) {
cat(donor)
cat("\n")
# train and validation split
X_celltype_cv_train <- X_celltype_driver[, donors != donor]
X_celltype_cv_test <- X_celltype_driver[, donors == donor]
y_cv_train <- y[donors != donor]
y_cv_test <- y[donors == donor]
# train on test data
set.seed(123)
rf.malignant.celltype.cv <- randomForest(
x = t(X_celltype_cv_train),
y = y_cv_train,
sampsize = rep(200, length(unique(y_cv_train))),
ntree = 200,
do.trace = FALSE
)
# predict validation data
y_cv_pred <-
predict(
rf.malignant.celltype.cv,
newdata = t(X_celltype_cv_test),
type = "prob"
)
y_cv_pred_max <- colnames(y_cv_pred)[max.col(y_cv_pred)]
# calculate accuracy
accuracy <- sum(y_cv_pred_max == y_cv_test) / length(y_cv_pred_max)
return(accuracy)
})
AML419A
AML329
AML707B
AML210A
names(cv_results_driver) <- donors_cv
donor_metadata %>%
filter(Sample %in% donors_cv, `Days from diagnosis` == "D0") %>%
select(Sample, `RHP Mutations`, `Common translocation`) %>%
unique()
lapply(cv_results, round, 3)
$AML419A
[1] 0.842
$AML329
[1] 0.337
$AML707B
[1] 0.45
$AML210A
[1] 0.711
lapply(cv_results_driver, round, 3)
$AML419A
[1] 0.87
$AML329
[1] 0.391
$AML707B
[1] 0.225
$AML210A
[1] 0.778
Excluding genes with driver mutations increases patient CV accuracy
slightly in 3/4 cases.
Cross-validation on driver mutations
TODO
To test the generalizability of the model on unseen mutations, cross
validation should be performed with holding out all samples with a
specific driver mutation.
“To exclude the possibility that the high frequency of cells with
detected NPM1 mutations affected the classifier, we generated a separate
classifier that does not consider NPM1 mutant calls. This separate
classifier had equally high specificity (99.8% of normal cells correctly
called normal), and sensitivity (93% of malignant cells correctly called
malignant) in 5-fold cross-validation. It is also consistent with the
original classifier: 97% of cells originally classified as normal were
classified as normal; 91% of cells originally classified as malignant
were classified as malignant. These results indicate that the classifier
is robust to the frequency of NPM1 mutations in the training set.”
Should have put the NPM1 sample in the non-NPM1 trained
classifier.
External test dataset
scRNA-seq from AML patients (NPM1) with sc genotyping.
Naldini, M.M., Casirati, G., Barcella, M. et al. Longitudinal
single-cell profiling of chemotherapy response in acute myeloid
leukemia. Nat Commun 14, 1285 (2023). https://doi.org/10.1038/s41467-023-36969-0
10 patients with NPM1mut AML, present in van Galen dataset. Majority
of NPM1mut AML sampels in van Galen dataset also have DNMT3 mutation.
DNMT3 status is not mentioned for this study.
3 patients with del(7) AML, not present in van Galen data.
Preprocessing information Feature-barcodes filtered matrices
from Cell Ranger were used as input for Seurat R package64,65 (version
3.2.3). Seurat objects were merged in a single full dataset. Cells with
a mitochondrial count ratio higher than 0.2 and <100 or >7000
expressed genes were removed from the dataset. UMI counts were log
normalized and scaled for a factor of 10,000.
The top 20% most variable genes were selected for downstream analysis.
Cell cycle scores were assigned with the CellCycleScoring function using
a reference gene lists66. We scaled data and regressed out unwanted
variability by passing UMI count, percent of mitochondrial genes and
cell cycle difference defined variables to the vars.to.regress argument.
Cell cycle difference was defined as the difference between S phase and
G/2 M phase module scores. Downstream analysis was performed on the top
100 principal components (PCA). In order to reduce patient-related and
10x chemistry version (v2 vs v3) bias, we performed data integration
using the Harmony package (v1.0)24.
Mutation annotation information NPM1-MF considers the UMI
counts supporting either NPM1 mutant or WT allele to classify cells as
MUT (≥1 UMI for the mutant allele) WT (>5 WT transcripts, no mutant
transcripts) ND – not detected (cells with ≤ 5 WT transcripts and no
mutant transcripts) NoCall (cells without coverage over the NPM1
mutation region)
Check patient metadata.
Do not provide information on age, Sex or clinincal blast count.
metadata_npm1_patients <- readxl::read_xlsx("naldini_et_al/41467_2023_36969_MOESM5_ESM.xlsx")
metadata_npm1_patients
Check cell metadata.
I can’t figure out what the orig.ident (e.g. M03) is. It is the ID used
in the expression matrix. So maybe the capture, but it doesn’t make much
sense to have 9 cells from PT12 and 570 cells from PT13 in M44. Why
would there be leakage between captures.
PT01, PT02, PT13 are primary refractory. PT06, PT07, PT12 are
long-term complete remission. PT08, PT09, PT10, PT15 are NPM1mut AML
relapse post-chemotherapy.
metadata_npm1_cells <- readRDS("naldini_et_al/GSE185991_Full_Patient_metadata.rds")
head(metadata_npm1_cells)
Check number of genotyped cells per sample to pick one for
testing.
PT02, combining diagnosis and D30 samples, has a lot of WT and MUT
cells, NPM1mut, primary refractory. Good patient to test if classifier
can extrapolate to other scRNA-seq datasets.
metadata_npm1_patients %>%
mutate(Sample = paste(PatientID, Timepoint, sep = "_")) %>%
group_by(Sample, Classification) %>%
summarize(n_cells = sum(n)) %>%
ggplot(aes(x = Sample, y = n_cells, fill = Classification)) +
geom_bar(stat = "identity") +
theme_bw() +
theme(panel.grid = element_blank(), axis.text.x = element_text(angle = 45, hjust = 1))
`summarise()` has grouped output by 'Sample'. You can override using the `.groups` argument.

Captures with PT02 data. Not sure why there are only 9 cells in the
other capture.
metadata_npm1_cells %>%
filter(PatientID == "PT02") %>%
pull(orig.ident) %>%
table()
.
M25 M26
1563 2152
metadata_npm1_cells %>%
filter(PatientID == "PT07") %>%
pull(orig.ident) %>%
table()
.
M21 M22 M23 M24
520 3706 603 6389
Read in count data.
naldini_seurat_m25 <- Read10X(
data.dir = "naldini_et_al/GSE185991_RAW/M25/",
gene.column = 2,
cell.column = 1,
unique.features = TRUE,
strip.suffix = FALSE
)
colnames(naldini_seurat_m25) <- paste0("M25_", gsub("-1", "", colnames(naldini_seurat_m25)))
naldini_seurat_m26 <- Read10X(
data.dir = "naldini_et_al/GSE185991_RAW/M26/",
gene.column = 2,
cell.column = 1,
unique.features = TRUE,
strip.suffix = FALSE
)
colnames(naldini_seurat_m26) <- paste0("M26_", gsub("-1", "", colnames(naldini_seurat_m26)))
# combine captures and merge
naldini_seurat_pt02 <- cbind(naldini_seurat_m25, naldini_seurat_m26)
naldini_seurat_pt02 <- CreateSeuratObject(counts = naldini_seurat_pt02, min.cells = 0, min.features = 200)
ncol(naldini_seurat_pt02)
[1] 4677
naldini_seurat_m25 <- NULL
naldini_seurat_m26 <- NULL
gc()
used (Mb) gc trigger (Mb) limit (Mb) max used (Mb)
Ncells 14875814 794.5 22323122 1192.2 NA 22323122 1192.2
Vcells 5053453562 38554.8 9354963862 71372.8 102400 9327473519 71163.0
# subset to cells present in metadata
naldini_seurat_pt02 <- subset(naldini_seurat_pt02, cells = rownames(metadata_npm1_cells))
ncol(naldini_seurat_pt02)
[1] 3714
# add metadata to Seurat object
naldini_seurat_pt02 <- AddMetaData(naldini_seurat_pt02, metadata_npm1_cells)
table(naldini_seurat_pt02$orig.ident)
M25 M26
1563 2151
naldini_seurat_m21 <- Read10X(
data.dir = "naldini_et_al/GSE185991_RAW/M21/",
gene.column = 2,
cell.column = 1,
unique.features = TRUE,
strip.suffix = FALSE
)
colnames(naldini_seurat_m21) <- paste0("M21_", gsub("-1", "", colnames(naldini_seurat_m21)))
naldini_seurat_m22 <- Read10X(
data.dir = "naldini_et_al/GSE185991_RAW/M22/",
gene.column = 2,
cell.column = 1,
unique.features = TRUE,
strip.suffix = FALSE
)
colnames(naldini_seurat_m22) <- paste0("M22_", gsub("-1", "", colnames(naldini_seurat_m22)))
naldini_seurat_m23 <- Read10X(
data.dir = "naldini_et_al/GSE185991_RAW/M23/",
gene.column = 2,
cell.column = 1,
unique.features = TRUE,
strip.suffix = FALSE
)
colnames(naldini_seurat_m23) <- paste0("M23_", gsub("-1", "", colnames(naldini_seurat_m23)))
naldini_seurat_m24 <- Read10X(
data.dir = "naldini_et_al/GSE185991_RAW/M24/",
gene.column = 2,
cell.column = 1,
unique.features = TRUE,
strip.suffix = FALSE
)
colnames(naldini_seurat_m24) <- paste0("M24_", gsub("-1", "", colnames(naldini_seurat_m24)))
# combine captures and merge
naldini_seurat_pt07 <- cbind(naldini_seurat_m21, naldini_seurat_m22, naldini_seurat_m23, naldini_seurat_m24)
naldini_seurat_pt07 <- CreateSeuratObject(counts = naldini_seurat_pt07, min.cells = 0, min.features = 200)
ncol(naldini_seurat_pt07)
[1] 13396
naldini_seurat_m21 <- NULL
naldini_seurat_m22 <- NULL
naldini_seurat_m23 <- NULL
naldini_seurat_m24 <- NULL
gc()
used (Mb) gc trigger (Mb) limit (Mb) max used (Mb)
Ncells 14878898 794.7 22323122 1192.2 NA 22323122 1192.2
Vcells 5073623895 38708.7 9354963862 71372.8 102400 9327473519 71163.0
# subset to cells present in metadata
naldini_seurat_pt07 <- subset(naldini_seurat_pt07, cells = rownames(metadata_npm1_cells))
ncol(naldini_seurat_pt07)
[1] 11216
# add metadata to Seurat object
naldini_seurat_pt07 <- AddMetaData(naldini_seurat_pt07, metadata_npm1_cells)
table(naldini_seurat_pt07$orig.ident)
M21 M22 M23 M24
520 3704 603 6389
Filter cells for >3000 UMI, 1000 genes, <10% mitochondrial
transcripts PT02. PT07 seems to have lower quality. Use less stringent
filters.
VlnPlot(naldini_seurat_pt02, features = c("nCount_RNA", "nFeature_RNA", "percent.mt"), log = T, pt.size = 0)
Warning: Default search for "data" layer in "RNA" assay yielded no results; utilizing "counts" layer instead.

VlnPlot(naldini_seurat_pt07, features = c("nCount_RNA", "nFeature_RNA", "percent.mt"), log = T, pt.size = 0)
Warning: Default search for "data" layer in "RNA" assay yielded no results; utilizing "counts" layer instead.

Seems like PT02 is female, PT07 is male.
VlnPlot(naldini_seurat_pt02, features = c("XIST"))
Warning: Default search for "data" layer in "RNA" assay yielded no results; utilizing "counts" layer instead.

VlnPlot(naldini_seurat_pt07, features = c("XIST"))
Warning: Default search for "data" layer in "RNA" assay yielded no results; utilizing "counts" layer instead.Warning: All cells have the same value of XIST.

naldini_seurat_pt02 <-
subset(naldini_seurat_pt02,
subset = nCount_RNA > 3000 & nFeature_RNA > 1000 & percent.mt < 10)
naldini_seurat_pt07 <-
subset(naldini_seurat_pt07,
subset = nCount_RNA > 1000 & nFeature_RNA > 500 & percent.mt < 10)
table(naldini_seurat_pt02$orig.ident)
M25 M26
1347 1916
table(naldini_seurat_pt07$orig.ident)
M21 M22 M23 M24
359 2498 563 5736
naldini_seurat_pt02_pt07 <-
merge(naldini_seurat_pt02, naldini_seurat_pt07)
naldini_seurat_pt02_pt07
An object of class Seurat
33538 features across 12419 samples within 1 assay
Active assay: RNA (33538 features, 0 variable features)
2 layers present: counts.1, counts.2
naldini_seurat_pt02_pt07 <-
merge(
naldini_seurat_pt02,
y = naldini_seurat_pt07,
add.cell.ids = c("PT02", "PT07"),
project = "Naldini"
)
naldini_seurat_pt02_pt07
An object of class Seurat
33538 features across 12419 samples within 1 assay
Active assay: RNA (33538 features, 0 variable features)
2 layers present: counts.1, counts.2
Normalize gene expression.
naldini_seurat_pt02_pt07 <- NormalizeData(naldini_seurat_pt02_pt07)
Normalizing layer: counts.1
Performing log-normalization
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Normalizing layer: counts.2
Performing log-normalization
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Predict HSPC cell type
X <-
cbind(
naldini_seurat_pt02_pt07@assays$RNA@layers$data.1,
naldini_seurat_pt02_pt07@assays$RNA@layers$data.2
)
rownames(X) <- rownames(naldini_seurat_pt02_pt07)
Some features used in classifier are missing in data, either due to
gene ID mismatches or because the genes are not expressed. Both van
Galen and Naldini were aligned to hg38, so it is more likely lack of
expression. Will set missing genes to 0.
features_missing <-
rownames(rf.celltype.inner$importance)[!rownames(rf.celltype.inner$importance) %in% rownames(X)]
features_missing_mat <-
matrix(nrow = length(features_missing),
ncol = ncol(X),
data = 0)
rownames(features_missing_mat) <- features_missing
colnames(features_missing_mat) <- colnames(X)
X <- rbind(X, features_missing_mat)
X <- X[rownames(rf.celltype.inner$importance), ]
naldini_seurat_pt02_pt07_celltype_pred <- predict(rf.celltype.inner, newdata = t(X), type = "prob")
Predict malignant cells
X <-
cbind(
naldini_seurat_pt02_pt07@assays$RNA@layers$data.1,
naldini_seurat_pt02_pt07@assays$RNA@layers$data.2
)
X <- rbind(X, t(naldini_seurat_pt02_pt07_celltype_pred))
rownames(X) <-
c(
rownames(naldini_seurat_pt02_pt07),
colnames(naldini_seurat_pt02_pt07_celltype_pred)
)
features_missing <-
rownames(rf.malignant.celltype$importance)[!rownames(rf.malignant.celltype$importance) %in% rownames(X)]
features_missing_mat <-
matrix(nrow = length(features_missing),
ncol = ncol(X),
data = 0)
rownames(features_missing_mat) <- features_missing
colnames(features_missing_mat) <- colnames(X)
X <- rbind(X, features_missing_mat)
naldini_seurat_pt02_pt07_malignant_pred <-
predict(rf.malignant.celltype,
newdata = t(X),
type = "prob")
Warning: sparse->dense coercion: allocating vector of size 1.1 GiB
naldini_seurat_pt02_pt07_malignant_pred_max <-
colnames(naldini_seurat_pt02_pt07_malignant_pred)[max.col(naldini_seurat_pt02_pt07_malignant_pred)]
Results
Compare malignant vs. healthy prediction with genotype data.
naldini_seurat_pt02_malignant_pred_max <- naldini_seurat_pt02_pt07_malignant_pred_max[naldini_seurat_pt02_pt07$PatientID == "PT02"]
table(
naldini_seurat_pt02_malignant_pred_max,
naldini_seurat_pt02@meta.data$Classification
)
naldini_seurat_pt02_malignant_pred_max MUT ND NoCall WT
healthy 9 410 38 578
malignant 1118 717 190 203
round(t(t(
table(
naldini_seurat_pt02_malignant_pred_max,
naldini_seurat_pt02@meta.data$Classification
)
) / as.vector(
table(naldini_seurat_pt02@meta.data$Classification)
)), 3)
naldini_seurat_pt02_malignant_pred_max MUT ND NoCall WT
healthy 0.008 0.364 0.167 0.740
malignant 0.992 0.636 0.833 0.260
naldini_seurat_pt07_malignant_pred_max <-
naldini_seurat_pt02_pt07_malignant_pred_max[naldini_seurat_pt02_pt07$PatientID == "PT07"]
table(
naldini_seurat_pt07_malignant_pred_max,
naldini_seurat_pt07@meta.data$Classification
)
naldini_seurat_pt07_malignant_pred_max MUT ND NoCall WT
healthy 10 2166 2894 121
malignant 775 922 1816 452
round(t(t(
table(
naldini_seurat_pt07_malignant_pred_max,
naldini_seurat_pt07@meta.data$Classification
)
) / as.vector(
table(naldini_seurat_pt07@meta.data$Classification)
)), 3)
naldini_seurat_pt07_malignant_pred_max MUT ND NoCall WT
healthy 0.013 0.701 0.614 0.211
malignant 0.987 0.299 0.386 0.789
Test on sample with a mutation not present in van Galen data.
del(7) AML: deletion in chromosome 7.
Classification of del(7) cells into malignant/wt:
“We leveraged the AddModuleScore function for evaluating the expression
level of a Chr 7 signature by using as input gene list all genes located
on it. The observed distribution of Chr 7 module scores in the datasets
followed a bimodal distribution allowing us to classify cells as AML or
non-AML by running a k-means clustering (n = 2) on the vector of Chr 7
signature scores and labelling cells in the high score group as non-AML
and those in the low score group as AML.”
Unfortunately, not included in metadata.
PT11, PT17: refractory disease; PT18: early relapse
metadata_del7_cells <- readRDS("naldini_et_al/GSE185991_AML_Del7_metadata.rds")
table(metadata_del7_cells$orig.ident, metadata_del7_cells$PatientID)
PT11 PT17 PT18
M36 6665 0 0
M38 1942 0 0
M84 0 7924 0
M85 0 1978 0
M88 0 0 5847
M89 0 0 3440
M92 0 5512 0
Computing module score with all genes on chr7 might be very slow.
Download cellranger gtf reference, get all genes on chr7.
source="/Users/holzehenrietta/Documents/personal/phd_applications/muds_task_marr/reference_sources"
mkdir -p "$source"
gtf_url="http://ftp.ebi.ac.uk/pub/databases/gencode/Gencode_human/release_44/gencode.v44.primary_assembly.annotation.gtf.gz"
gtf_in="${source}/gencode.v44.primary_assembly.annotation.gtf"
if [ ! -f "$gtf_in" ]; then
curl -sS "$gtf_url" | zcat > "$gtf_in"
fi
grep "^chr7" $gtf_in | awk '$3 == "gene"' | sed 's/.*gene_name "\([^"]*\)".*/\1/g' > ${source}/gencode.v44_gene_names_chr7.txt
grep "^chrY" $gtf_in | awk '$3 == "gene"' | sed 's/.*gene_name "\([^"]*\)".*/\1/g' > ${source}/gencode.v44_gene_names_chrY.txt
chr7_genes <-
read_lines(
"/Users/holzehenrietta/Documents/personal/phd_applications/muds_task_marr/reference_sources/gencode.v44_gene_names_chr7.txt"
)
Subset to genes expressed in >= 10% of cells in del(7) scRNA-seq
data to speed up analysis.
LS0tCnRpdGxlOiAiTVVEUyBNYXJyIEV4ZXJjaXNlIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6IAogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNAphdXRob3I6IEhlbnJpZXR0YSBIb2x6ZQpkYXRlOiAiMjEgTm92ZW1iZXIgMjAyNCIKLS0tCgojIyBUYXNrIGRlc2NyaXB0aW9uCgpJbiBhIDIwMTkgcHVibGljYXRpb24gKGRvaToxMC4xMDE2L2ouY2VsbC4yMDE5LjAxLjAzMSksIFBldGVyIHZhbiBHYWxlbiBhbmQgY29sbGVhZ3VlcyBhbmFseXplZCB0cmFuc2NyaXB0b21pYyBwcm9maWxlcyBvZiBib25lIG1hcnJvdyBhc3BpcmF0ZXMgZnJvbSAxNiBBTUwgcGF0aWVudHMgYW5kIGZpdmUgaGVhbHRoeSBkb25vcnMuCgpZb3VyIGZpcnN0IHRhc2sgaW4gdGhpcyBleGVyY2lzZSBpcyB0byBwcm92aWRlIGEgY29uY2lzZSBvdmVydmlldyBvZiB0aGUgZGF0YXNldCwgaW5jbHVkaW5nIHN1bW1hcnkgc3RhdGlzdGljcyBzdWNoIGFzIHRoZSBjb21wb3NpdGlvbiBvZiBjZWxsLXR5cGUgZnJlcXVlbmNpZXMgYWNyb3NzIGRvbm9ycyBhbmQgYSB2aXN1YWxpemF0aW9uIG9mIHRoZSBkYXRhIGluIHR3byBkaW1lbnNpb25zIChlLmcuLCB1c2luZyBVTUFQIG9yIHQtU05FIGVtYmVkZGluZ3MpLiAKCkFuIGludGVyZXN0aW5nIGZlYXR1cmUgb2YgdGhlIGRhdGFzZXQgaXMgdGhlIGF2YWlsYWJpbGl0eSBvZiBsYWJlbHMgZGlzdGluZ3Vpc2hpbmcgbWFsaWduYW50IGFuZCBub24tbWFsaWduYW50IHNpbmdsZSBjZWxscy4gVGhlIHNlY29uZCB0YXNrIGlzIHRvIGV4cGxvaXQgdGhlc2UgbGFiZWxzIHRvIGJ1aWxkIGEgc3VpdGFibGUgTUwgY2xhc3NpZmllciB0aGF0IHByZWRpY3RzIHRoZSBub3JtYWwgb3IgbWFsaWduYW50IGlkZW50aXR5IG9mIHNpbmdsZSBjZWxscy4gClBsZWFzZSBkZXRhaWwgb24gaG93IHlvdXIgY2xhc3NpZmllciB3b3Jrcywgd2hldGhlciBpdCBpcyBpbnRlcnByZXRhYmxlLCBpdHMgcGVyZm9ybWFuY2UsIGFuZCB0aGUgdHJhaW4tdGVzdCBzcGxpdCB5b3UgdXNlIGZvciB5b3VyIG1hY2hpbmUgbGVhcm5pbmcgZXhwZXJpbWVudC4gIAoKCiMjIyBSZWxldmFuY2Ugb2YgdGhlIHByb2JsZW0KCioqVGFzayAxKioKCkl0IGlzIGltcG9ydGFudCB0byBnZXQgYSBnb29kIHVuZGVyc3RhbmRpbmcgb2YgYSBkYXRhc2V0IGJlZm9yZSB0cmFpbmluZyBhbnkga2luZCBvZiBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobSBvbiBpdC4gCkltcG9ydGFudCBmYWN0b3JzIHRvIGJlIGF3YXJlIG9mIGFyZSBiaWFzZXMgaW4gdGhlIGRhdGEsIGluIHBhcnRpY3VsYXIsIGJpYXNlcyBpbiB0aGUgcGF0aWVudCBtZXRhZGF0YS4gIApGdXJ0aGVybW9yZSwgdGhlIHF1YWxpdHkgb2YgdGhlIGRhdGEgaGFzIHRvIGJlIGFzc2Vzc2VkLCBzdWNoIGFzIHNhbXBsZSBhbmQgY2VsbCBxdWFsaXR5LiAgCkxhc3RseSwgdHJhbnNjcmlwdG9taWNzIGRhdGEgaXMgaGlnaGx5IGRpbWVuc2lvbmFsIGFuZCBpbiBvcmRlciB0byByZWNvZ25pemUgcGF0dGVybnMgaW4gdGhlIGRhdGEsIGl0IGlzIHVzZWZ1bCB0byBlbWJlZCB0aGUgZGF0YSBpbnRvIGEgMkQgc3BhY2UgdGhhdCBjYW4gYmUgdmlzdWFsaXplZC4gCkVtYmVkZGluZyBvZiBkYXRhIGludG8gYSBsb3dlciBkaW1lbnNpb25hbCBzcGFjZSwgZS5nLiB2aWEgUENBLCBhbHNvIHNlcnZlcyB0byByZW1vdmUgbm9pc2UuCgoqKlRhc2sgMioqCgpEZXBlbmRpbmcgb24gZGlzZWFzZSBidXJkZW4sIHRoZXJlIGFyZSBoZWFsdGh5IGFuZCBtYWxpZ25hbnQgY2VsbHMgcHJlc2VudCBpbiB0aGUgYm9uZSBtYXJyb3cgb2YgcGF0aWVudHMgd2l0aCBBTUwuICAKSW4gb3JkZXIgdG8gaW52ZXN0aWdhdGUgZGlzZWFzZSBtZWNoYW5pc21zIG9mIEFNTCwgd2UgbmVlZCB0byBiZSBhYmxlIHRvIGNsZWFybHkgZGlmZmVyZW50aWF0ZSBiZXR3ZWVuIGhlYWx0aHkgYW5kIG1hbGlnbmFudCBjZWxscy4gCk9ubHkgdGhlbiwgd2UgY2FuIGxvb2sgYXQgd2hhdCBiaW9sb2dpY2FsIHByb2Nlc3NlcyBpbiBjZWxsIGRpZmZlcmVudGlhdGlvbiBhcmUgZGlzcnVwdGVkLiAKVGhlIGRpZmZlcmVudGlhdGlvbiBpcyBhbHNvIGltcG9ydGFudCB0byBpZGVudGlmeSB0cmVhdG1lbnQgdGFyZ2V0cyB0aGF0IGRvIG5vdCBraWxsIHRoZSByZW1haW5pbmcgaGVhbHRoeSBIU1BDcy4gIApQYXJ0aWN1bGFybHkgaW4gTVJEIHNhbXBsZXMgaXQgaXMgdmVyeSBpbXBvcnRhbnQgdG8gaWRlbnRpZnkgdGhlIHN1YnNldCBvZiBtYWxpZ25hbnQgY2VsbHMgdGhhdCBpcyByZXNpc3RhbnQvdG9sZXJhbnQgdG8gdHJlYXRtZW50LiAgCkluIEFNTCwgaGVhbHRoeSBhbmQgbWFsaWduYW50IHByb2dlbml0b3IgYW5kIG15ZWxvaWQgY2VsbHMgYXJlIG5vdCBkaXN0aW5ndWlzaGFibGUgYnkgY2VsbCBzdXJmYWNlIG1hcmtlcnMgaS5lLiwgaXQgaXMgbm90IHBvc3NpYmxlIHRvIGlzb2xhdGUgaGVhbHRoeS9tYWxpZ25hbnQgY2VsbHMgYnkgRkFDUyAoeWV0KS4gIApUaHVzLCBzY1JOQS1zZXEgZGF0YSBvZiBBTUwgQk0gc2FtcGxlcyBjb250YWluIGJvdGggZ3JvdXBzIG9mIGNlbGxzIGFuZCB3ZSBoYXZlIHRodXMgZmFyIG5vdCBiZWVuIGFibGUgdG8gZGlzdGluZ3Vpc2ggdGhlbSBiYXNlZCBvbiB0aGVpciB0cmFuc2NyaXB0b21lLgoKVmFuIEdhbGVuIGV0IGFsLiBlbXBsb3llZCBhIG5vdmVsIHRlY2hub2xvZ3kgdG8gZ2Vub3R5cGUgYSBzdWJzZXQgb2YgY2VsbHMgcHJlc2VudCBpbiB0aGUgc2NSTkEtc2VxIGRhdGEgYW5kIHVzZWQgdGhlc2UgZ2Vub3R5cGVkIGNlbGxzIHRvIGJ1aWxkIGEgY2xhc3NpZmllciB0byBkaXN0aW5ndWlzaCB0aGUgYnVsayBvZiB0aGUgY2VsbHMgaW50byBtYWxpZ25hbnQgYW5kIG5vbi1tYWxpZ25hbnQgY2VsbHMuICAKSG93ZXZlciwgc2MgZ2Vub3R5cGluZyBjb21lcyBhdCBhbiBleHRyYSBjb3N0IGFuZCBsYWJvciBhbmQgaXMgbm90IGF2YWlsYWJsZSBmb3IgYWxsIGRhdGFzZXRzLiAKVGhlcmVmb3JlLCBpdCB3b3VsZCBiZSBleHRyZW1lbHkgdXNlZnVsIHRvIGhhdmUgYSBnZW5lcmFsIGNsYXNzaWZpZXIgb2Ygbm9ybWFsIHZzLiBtYWxpZ25hbnQgY2VsbHMgaW4gQU1MIHNhbXBsZXMsIHRoYXQgaXMgYXBwbGljYWJsZSB0byBvdGhlciBzY1JOQS1zZXEgZGF0YXNldHMuIAoKIyMjIHZhbiBHYWxlbiAyMDE5IGRhdGFzZXQKCltQYXBlcl0oaHR0cHM6Ly93d3cuY2VsbC5jb20vY2VsbC9mdWxsdGV4dC9TMDA5Mi04Njc0KDE5KTMwMDk0LTcpCgpbT3JpZ2luYWwgYW5hbHlzaXMgb2YgdGhlIGRhdGFdKGh0dHBzOi8vZ2l0aHViLmNvbS9CZXJuc3RlaW5MYWIvYW1sMjAxOSkKCltSZS1hbmFseXNpcyBvZiBkYXRhXShodHRwczovL2dpdGh1Yi5jb20vcGV0ZXJ2YW5nYWxlbi9yZWFuYWx5emUtYW1sMjAxOSkKCkRhdGEgZG93bmxvYWRlZCB3aXRoIGB3Z2V0IGh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3MvMzk5eDA0NXpjNTdmaXV0L1NldXJhdF9BTUwucmRzYAoKTVVUWi0zIGFuZCBPQ0ktQU1MMyBhcmUgQU1MIGNlbGwgbGluZXMuCgpCTSBzYW1wbGVzIGFyZSBmcm9tIGhlYWx0aHkgZG9ub3JzLiAKQ2VsbHMgZnJvbSBvbmUgaGVhbHRoeSBkb25vciB3ZXJlIHNvcnRlZCBmb3IgQ0QzNCsgKEhTUENzKSBhbmQgQ0QzNCtDRDM4LSAoSFNDcyksIHRvIGVucmljaCBmb3IgcmFyZSBlYXJseSBwcm9nZW5pdG9yIHBvcHVsYXRpb25zLgoKQWxsIG90aGVyIHNhbXBsZXMgYXJlIGZyb20gcGF0aWVudHMgZGlhZ25vc2VkIHdpdGggQU1MLCB3aXRoIGRpZmZlcmVudCBkcml2ZXIgbXV0YXRpb25zLiAKCkFNTCBzYW1wbGVzIHdlcmUgY29sbGVjdGVkIGF0IGRheSAwIGFuZCBmb3IgYSBzdWJzZXQgb2Ygc2FtcGxlcyBhdCBkaWZmZXJlbnQgdGltZSBwb2ludHMgcG9zdCBkaWFnbm9zaXMgKGFuZCBwb3N0IGNoZW1vKS4KCiMjIFRhc2sgMTogRXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcwoKYGBge3IgcmVzdWx0cz1GfQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeShTZXVyYXQpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGNoYW1lbGVvbikKbGlicmFyeShyYW5kb21Gb3Jlc3QpCmBgYAoKCjwhLS0gYGBge3J9IC0tPgo8IS0tICMgbGlicmFyeSgicmV0aWN1bGF0ZSIpIC0tPgo8IS0tICMgcHlfaW5zdGFsbCgicHl0aG9uLWlncmFwaCIpIC0tPgo8IS0tICMgcHlfaW5zdGFsbCgibGVpZGVuYWxnIiwgZm9yZ2UgPSBUUlVFKSAtLT4KPCEtLSAjIGluc3RhbGwucGFja2FnZXMoImxlaWRlbiIpIC0tPgo8IS0tIGBgYCAtLT4KCiMjIyBQcmVwcm9jZXNzIG1ldGFkYXRhCgpMb2FkIHNjUk5BLXNlcSBkYXRhIG9iamVjdCBhbmQgbWV0YWRhdGEuIAoKYGBge3J9CmdhbGVuX2FtbCA8LSByZWFkUkRTKCJyZWFuYWx5emUtYW1sMjAxOS9TZXVyYXRfQU1MLnJkcyIpCmRvbm9yX21ldGFkYXRhIDwtCiAgcmVhZHhsOjpyZWFkX3hsc3goCiAgICAiU2NpZW5jZURpcmVjdF9maWxlc18yMU5vdjIwMjRfMDgtMDktMzAuOTg3LzEtczIuMC1TMDA5Mjg2NzQxOTMwMDk0Ny1tbWMxLnhsc3giCiAgKQpgYGAKCkxvYWQgSFNQQyBjZWxsIHR5cGVzIGFubm90YXRlZCBieSBiYWNrc3BpbiBjbHVzdGVyIGFuZCBhZGQgdG8gbWV0YWRhdGEgb2YgU2V1cmF0IG9iamVjdC4KRnJvbSBbYW1sMjAxOSBnaXRodWIgcmVwb10oaHR0cHM6Ly9naXRodWIuY29tL0Jlcm5zdGVpbkxhYi9hbWwyMDE5L3RyZWUvbWFzdGVyKS4KCmBgYHtyIHJlc3VsdHM9Rn0KYmFja3NwaW5fY2VsbHR5cGVzIDwtCiAgcmVhZF90c3YoImFtbDIwMTkvMDQgUmFuZG9tIGZvcmVzdCBjbGFzc2lmaWVyL0JNXzY5MTVjZWxscy5CYWNrU1BJTi50eHQiKQoKYmFja3NwaW5fY2VsbHR5cGVzIDwtCiAgY29sdW1uX3RvX3Jvd25hbWVzKGJhY2tzcGluX2NlbGx0eXBlcywgdmFyID0gImNlbGwiKSAlPiUgZHBseXI6OnJlbmFtZShiYWNrc3Bpbl9jZWxsdHlwZSA9IGNsdXN0ZXIpCnJvd25hbWVzKGJhY2tzcGluX2NlbGx0eXBlcykgPC0gZ3N1YigiLSIsICJcXC4iLCByb3duYW1lcyhiYWNrc3Bpbl9jZWxsdHlwZXMpKQoKZ2FsZW5fYW1sIDwtIEFkZE1ldGFEYXRhKGdhbGVuX2FtbCwgbWV0YWRhdGEgPSBiYWNrc3Bpbl9jZWxsdHlwZXMpCmBgYAoKCmBgYHtyfQpoZWFkKGdhbGVuX2FtbEBtZXRhLmRhdGEpCmhlYWQoZG9ub3JfbWV0YWRhdGEpCmBgYAoKTWV0YWRhdGEgaW5jbHVkZWQgaW4gU2V1cmF0IG9iamVjdAotIG9yaWcuaWRlbnQgKHBhdGllbnQgSUQgKyBkYXkgZnJvbSBkaWFnbm9zaXMpCi0gTnVtYmVyT2ZSZWFkcwotIEN5Y2xpbmdTY29yZQotIEN5Y2xpbmdCaW5hcnkKLSBNdXRUcmFuc2NyaXB0cyAobnVtYmVyIG9mIHRyYW5zY3JpcHQgZm9yIHd0IGFsbGVsZSkKLSBXdFRyYW5zY3JpcHRzIChudW1iZXIgb2YgdHJhbnNjcmlwdCBmb3IgbXV0YXRlZCBhbGxlbGUpCi0gUHJlZGljdGlvblJlZmluZWQgKGNsYXNzaWZpY2F0aW9uIG9mIGNlbGxzIGludG8gbWFsaWduYW50LCBoZWFsdGh5IGFuZCB1bmNlcnRhaW4pCi0gQ2VsbFR5cGUgKGNsYXNzaWZpY2F0aW9uIGludG8gMjEgaGVhbHRoeSBhbmQgbWFsaWduYW50IGNlbGwgdHlwZXMpCi0gbkNvdW50X1JOQQotIG5GZWF0dXJlX1JOQQoKClJlbW92ZSBjZWxscyBhbmQgbWV0YWRhdGEgYXNzb2NpYXRlZCB0byBjZWxsIGxpbmVzLgoKYGBge3J9CmdhbGVuX2FtbCRTYW1wbGVDbGFzcyA8LQogIGlmZWxzZSgKICAgIGdyZXBsKCJCTSIsIGdhbGVuX2FtbCRvcmlnLmlkZW50KSwKICAgICJIZWFsdGh5RG9ub3IiLAogICAgaWZlbHNlKAogICAgICBncmVwbCgiT0NJLkFNTDN8TVVUWjMiLCBnYWxlbl9hbWwkb3JpZy5pZGVudCksCiAgICAgICJDZWxsTGluZSIsCiAgICAgICJBbWxEb25vciIKICAgICkKICApCmdhbGVuX2FtbF9kb25vcnMgPC0gc3Vic2V0KGdhbGVuX2FtbCwgU2FtcGxlQ2xhc3MgIT0gIkNlbGxMaW5lIikKCmRvbm9yX21ldGFkYXRhIDwtIGRvbm9yX21ldGFkYXRhICU+JQogIGZpbHRlcighU2FtcGxlICVpbiUgYygiT0NJLUFNTDMiLCAiTVVUWjMiKSkKYGBgCgpDb3JyZWN0IHZhbHVlIGluIGJsYXN0IGNvdW50IG1ldGFkYXRhLiAKIjElICg3NiUgcHJvbW9uby1jeXRlcykiIGlzIGNvbnNpZGVyZWQgYXMgYmxhc3QgY291bnQgb2YgNzYlIGluIHRoZSBwYXBlciAoRmlnLiAyYikuCgpgYGB7cn0KZG9ub3JfbWV0YWRhdGEkYEJsYXN0IGNvdW50YCA8LQogIGFzLm51bWVyaWMoZ3N1YigiPDVcXCUiLCAiMC4wNSIsIGdzdWIoCiAgICAiPDFcXCUiLAogICAgIjAuMDEiLAogICAgZ3N1YigiLio3NlxcJS4qIiwgIjAuNzYiLCBkb25vcl9tZXRhZGF0YSRgQmxhc3QgY291bnRgKQogICkpKQpkb25vcl9tZXRhZGF0YSRBZ2UgPC0gYXMubnVtZXJpYyhkb25vcl9tZXRhZGF0YSRBZ2UpCmBgYAoKCk1lcmdlIGRvbm9yIG1ldGFkYXRhIGludG8gU2V1cmF0IG9iamVjdCBtZXRhZGF0YS4KCmBgYHtyfQojIG1hdGNoIHNhbXBsZSBJRCBiZXR3ZWVuIFNldXJhdCBvYmplY3QgYW5kIG1ldGFkYXRhIHRhYmxlCmRvbm9yX21ldGFkYXRhIDwtIGRvbm9yX21ldGFkYXRhICU+JQogIG11dGF0ZShTYW1wbGVJZCA9IGdzdWIoIkNEIiwgIiIsIGdzdWIoIiAiLCAiXFwuIiwgZ3N1YigKICAgICJcXCsiLCAicCIsIGdzdWIoIi0iLCAibiIsIFNhbXBsZSkKICApKSkpICU+JQogIG11dGF0ZShTYW1wbGVJZCA9IGlmZWxzZSgKICAgIGdyZXBsKCJCTSIsIFNhbXBsZUlkKSwKICAgIFNhbXBsZUlkLAogICAgcGFzdGUoU2FtcGxlSWQsIGBEYXlzIGZyb20gZGlhZ25vc2lzYCwgc2VwID0gIi4iKQogICkpCgojIG1lcmdlIGludG8gc2V1cmF0IG9iamVjdCBtZXRhZGF0YSwgcHJlc2VydmUgb3JkZXIgb2YgY2VsbHMKdG1wIDwtCiAgbWVyZ2UoCiAgICByb3duYW1lc190b19jb2x1bW4oZ2FsZW5fYW1sX2Rvbm9yc0BtZXRhLmRhdGEpLAogICAgZG9ub3JfbWV0YWRhdGEsCiAgICBieS54ID0gIm9yaWcuaWRlbnQiLAogICAgYnkueSA9ICJTYW1wbGVJZCIsCiAgICBhbGwueCA9IFQKICApCnRtcCA8LSBjb2x1bW5fdG9fcm93bmFtZXModG1wLCAicm93bmFtZSIpCmdhbGVuX2FtbF9kb25vcnNAbWV0YS5kYXRhIDwtIHRtcFtjb2xuYW1lcyhnYWxlbl9hbWxfZG9ub3JzKSxdCmBgYAoKCiMjIyBTZXggZGlzdHJpYnV0aW9uCgpUZXR0ZXJvLCBKLk0uLCBDbG9vcywgSi4gJiBCdWxsaW5nZXIsIEwuIEFjdXRlIG15ZWxvaWQgbGV1a2VtaWE6IGRvZXMgc2V4IG1hdHRlcj8uIExldWtlbWlhIDM4LCAyMzI54oCTMjMzMSAoMjAyNCkuIFtodHRwczovL2RvaS5vcmcvMTAuMTAzOC9zNDEzNzUtMDI0LTAyNDM1LXpdKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvczQxMzc1LTAyNC0wMjQzNS16L3RhYmxlcy8xKQogICAgCkFNTCBpcyBtb3JlIGZyZXF1ZW50IGluIG1hbGVzIChjYS4gNC41IHBlciAxMDAsMDAwKSB2cyBmZW1hbGVzIChjYS4gMy4wIHBlciAxMDAsMDAwKS4KQSBudW1iZXIgb2Ygc3R1ZGllcyBzaG93aW5nIGRpZmZlcmVuY2VzIGluIHRyZWF0bWVudCByZXNwb25zZSBpbiBtYWxlcyBhbmQgZmVtYWxlcyBkZW1vbnN0cmF0ZXMgdGhlIGltcG9ydGFuY2UgdG8gY29uc2lkZXIgc2V4LgoKVGhlIHZhbiBHYWxlbiBkYXRhc2V0IG9ubHkgaGFzIG1hbGUgaGVhbHRoeSBkb25vcnMgYW5kIGlzIGJpYXNlZCB0b3dhcmRzIG1hbGUgQU1MIGRvbm9ycyAoNjIuNSUpLiAKU2xpZ2h0bHkgbW9yZSBwcm9ub3VuY2VkIHdoZW4gY29uc2lkZXJpbmcgQU1MIHNhbXBsZXMgKDY2LjclKS4KCk9ubHkgY2VsbHMgZnJvbSBoZWFsdGh5IGRvbm9ycyBhbmQgZ2Vub3R5cGVkIG1hbGlnbmFudCBjZWxscyBmcm9tIEFNTCBjZWxscyB3ZXJlIHVzZWQgZm9yIHRyYWluaW5nIHRoZSBtYWxpZ25hbnQgdnMuIG5vcm1hbCBjbGFzc2lmaWVyLiAKSS5lLiwgYWxsIGhlYWx0aHkgY2VsbHMgaW4gdGhlIHRyYWluaW5nIGRhdGEgd2VyZSBtYWxlLiAgClRoaXMgd291bGQgdXN1YWxseSByZXN1bHQgaW4gYSBjbGFzc2lmaWNhdGlvbiBvZiBhbGwgZmVtYWxlIGNlbGxzIGFzIEFNTCBjZWxscy4gSG93ZXZlciwgdGhlcmUgaXMgYWxzbyBhIG1hbGUgQU1MIHNhbXBsZXMgd2l0aCBsb3NzIG9mIFkgKEFNTDcwN0IpLCB3aGljaCBtYWtlcyBzZXggbW9yZSBhbWJpZ3VvdXNseSBsaW5rZWQgdG8gbWFsaWduYW50IHN0YXR1cy4gCkZ1cnRoZXJtb3JlLCBleHByZXNzaW9uIG9mIHNleC1zcGVjaWZpYyBnZW5lcyBjb3VsZCBiZSBsb3cvc3BhcnNlLCBhbmQgbm90IGFsbCBjZWxscyBtaWdodCBiZSBkaXN0aW5ndWlzaGFibGUgYnkgc2V4IGJhc2VkIG9uIHRoZWlyIGV4cHJlc3Npb24uCgpgYGB7ciBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD02fQojIGRvbm9ycwpwMSA8LSBkb25vcl9tZXRhZGF0YSAlPiUKICBzZWxlY3QoU2FtcGxlLCBHZW5kZXIpICU+JQogIG11dGF0ZShTYW1wbGUgPSBnc3ViKCIgLioiLCAiIiwgU2FtcGxlKSkgJT4lCiAgdW5pcXVlKCkgJT4lCiAgbXV0YXRlKEdyb3VwID0gaWZlbHNlKGdyZXBsKCJCTSIsIFNhbXBsZSksICJIZWFsdGh5IiwgIkFNTCIpKSAlPiUKICBncm91cF9ieShHcm91cCwgR2VuZGVyKSAlPiUKICBzdW1tYXJpemUobl9kb25vcnMgPSBuKCkpICU+JQogIGdncGxvdChhZXMoZmlsbCA9IEdlbmRlciwgeSA9IG5fZG9ub3JzLCB4ID0gR3JvdXApKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICB0aGVtZV9idygpICsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiTm9uZSIsCiAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpCiAgKQoKIyBzYW1wbGVzCnAyIDwtIGRvbm9yX21ldGFkYXRhICU+JQogIHNlbGVjdChTYW1wbGUsIGBEYXlzIGZyb20gZGlhZ25vc2lzYCwgR2VuZGVyKSAlPiUKICB1bmlxdWUoKSAlPiUKICBtdXRhdGUoR3JvdXAgPSBpZmVsc2UoZ3JlcGwoIkJNIiwgU2FtcGxlKSwgIkhlYWx0aHkiLCAiQU1MIikpICU+JQogIGdyb3VwX2J5KEdyb3VwLCBHZW5kZXIpICU+JQogIHN1bW1hcml6ZShuX3NhbXBsZXMgPSBuKCkpICU+JQogIGdncGxvdChhZXMoZmlsbCA9IEdlbmRlciwgeSA9IG5fc2FtcGxlcywgeCA9IEdyb3VwKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUocGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQoKZ2dwdWJyOjpnZ2FycmFuZ2UocDEsIHAyLCBhbGlnbiA9ICJoIiwgd2lkdGhzID0gYygxLCAxLjMpKQpgYGAKCgojIyMgQ2FuY2VyIGRyaXZlciBtdXRhdGlvbnMKClBsb3Qgd2hpY2ggc2FtcGxlIGhhcyB3aGljaCBjYW5jZXIgZHJpdmVyIGdlbmUgbXV0YXRlZCAoc2VlIEZpZy4gMkIpLiAgCkFNTDcyMkIgQkNPUiBpbiBwYXBlciBGaWcuIDJiIGJ1dCBub3QgaW4gc3VwcGxlbWVudGFyeSBtZXRhZGF0YS4gIApBbGwgc2FtcGxlcyBoYXZlIGEgdW5pcXVlIGNvbWJpbmF0aW9uIG9mIG11dGF0aW9ucy4gCgpgYGB7cn0Kc2FtcGxlX211dGF0aW9uX21hdHJpeCA8LSBkb25vcl9tZXRhZGF0YSAlPiUKICBmaWx0ZXIoCiAgICAhYFJIUCBNdXRhdGlvbnNgICVpbiUgYygiTm90IHBlcmZvcm1lZCIsICJOb25lIERldGVjdGVkIiwgIlVua25vd24iLCAiTkEiKSAmCiAgICAgICFpcy5uYShgUkhQIE11dGF0aW9uc2ApCiAgKSAlPiUKICBtdXRhdGUoZHJpdmVyX211dGF0aW9ucyA9IGBSSFAgTXV0YXRpb25zYCkgJT4lCiAgc2VwYXJhdGVfbG9uZ2VyX2RlbGltKGNvbHMgPSBkcml2ZXJfbXV0YXRpb25zLCAiLy8vICIpICU+JQogIG11dGF0ZShkcml2ZXJfbXV0YXRpb25zID0gZ3N1YigiIC4qIiwgIiIsIGRyaXZlcl9tdXRhdGlvbnMpKSAlPiUKICAjIHVuaXF1ZSBmb3Igc2FtcGxlcyB3aXRoIG11bHRpcGxlIG11dGF0aW9ucyBpbiBzYW1lIGdlbmUKICB1bmlxdWUoKSAlPiUKICAjIGNvdW50IG51bWJlciBvZiBzYW1wbGVzIGZvciBlYWNoIGdlbmUgYW5kIHNvcnQgYnkgdGhhdAogIGdyb3VwX2J5KGRyaXZlcl9tdXRhdGlvbnMpICU+JQogIG11dGF0ZShuX3NhbXBsZXMgPSBuKCkpICU+JQogIGFycmFuZ2UoZGVzYyhuX3NhbXBsZXMpKSAlPiUKICBtdXRhdGUodmFsdWUgPSAxKQoKc2FtcGxlX211dGF0aW9uX21hdHJpeCA8LSBzYW1wbGVfbXV0YXRpb25fbWF0cml4ICU+JQogIG11dGF0ZSgKICAgIGRyaXZlcl9tdXRhdGlvbnMgPSBmYWN0b3IoCiAgICAgIGRyaXZlcl9tdXRhdGlvbnMsCiAgICAgIGxldmVscyA9IHVuaXF1ZShzYW1wbGVfbXV0YXRpb25fbWF0cml4JGRyaXZlcl9tdXRhdGlvbnMpLAogICAgICBvcmRlcmVkID0gVAogICAgKSwKICAgIFNhbXBsZSA9IGZhY3RvcigKICAgICAgU2FtcGxlLAogICAgICBsZXZlbCA9IHVuaXF1ZShzYW1wbGVfbXV0YXRpb25fbWF0cml4JFNhbXBsZSksCiAgICAgIG9yZGVyZWQgPSBUCiAgICApCiAgKQoKIyBtdXRhdGlvbnMKcDEgPC0gc2FtcGxlX211dGF0aW9uX21hdHJpeCAlPiUKICBnZ3Bsb3QoYWVzKHggPSBkcml2ZXJfbXV0YXRpb25zLCB5ID0gU2FtcGxlKSkgKwogIGdlb21fdGlsZShmaWxsID0gImRhcmtyZWQiKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoCiAgICBhbmdsZSA9IDkwLAogICAgdmp1c3QgPSAwLjUsCiAgICBoanVzdCA9IDEKICApKSArCiAgdGhlbWUocGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkKCiMgcmVhcnJhbmdlbWVudHMKcDIgPC0gc2FtcGxlX211dGF0aW9uX21hdHJpeCAlPiUKICBnZ3Bsb3QoYWVzKHggPSBgQ29tbW9uIHRyYW5zbG9jYXRpb25gLCB5ID0gU2FtcGxlKSkgKwogIGdlb21fdGlsZShmaWxsID0gImRhcmtyZWQiKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoCiAgICBhbmdsZSA9IDkwLAogICAgdmp1c3QgPSAwLjUsCiAgICBoanVzdCA9IDEKICApKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCkKICApICsKICB5bGFiKCIiKQoKZ2dwdWJyOjpnZ2FycmFuZ2UoCiAgcDEsCiAgcDIsCiAgbmNvbCA9IDIsCiAgbnJvdyA9IDEsCiAgYWxpZ24gPSAiaCIsCiAgd2lkdGhzID0gYyg0LCAxKQopCmBgYAoKPCEtLSBBZGQgZHJpdmVyIG11dGF0aW9ucyBhcyBtZXRhZGF0YSB0byBTZXVyYXQgb2JqZWN0LiAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIHNhbXBsZV9hbWxfc3VidHlwZV9sYWJlbCA8LSBzYW1wbGVfbXV0YXRpb25fbWF0cml4ICU+JSAtLT4KPCEtLSAgIGdyb3VwX2J5KFNhbXBsZSkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKGFtbF9zdWJ0eXBlID0gcGFzdGUwKGRyaXZlcl9tdXRhdGlvbnMsIGNvbGxhcHNlID0gIiwiKSkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKGFtbF9zdWJ0eXBlID0gZ3N1YigiLE5BIiwgIiIsIHBhc3RlKGFtbF9zdWJ0eXBlLCBgQ29tbW9uIHRyYW5zbG9jYXRpb25gLCBzZXAgPSAiLCIpKSkgJT4lIC0tPgo8IS0tICAgc2VsZWN0KFNhbXBsZSwgYW1sX3N1YnR5cGUpICU+JSAtLT4KPCEtLSAgIHVuaXF1ZSgpIC0tPgoKPCEtLSB0bXAgPC0gLS0+CjwhLS0gICBtZXJnZShyb3duYW1lc190b19jb2x1bW4oZ2FsZW5fYW1sX2Rvbm9yc0BtZXRhLmRhdGEpLCAtLT4KPCEtLSAgICAgICAgIHNhbXBsZV9hbWxfc3VidHlwZV9sYWJlbCwgLS0+CjwhLS0gICAgICAgICBieSA9ICJTYW1wbGUiKSAtLT4KPCEtLSB0bXAgPC0gY29sdW1uX3RvX3Jvd25hbWVzKHRtcCwgInJvd25hbWUiKSAtLT4KCjwhLS0gZ2FsZW5fYW1sX2Rvbm9yc0BtZXRhLmRhdGEkRHJpdmVyTXV0YXRpb25zIDwtIC0tPgo8IS0tICAgdG1wW2NvbG5hbWVzKGdhbGVuX2FtbF9kb25vcnMpLF0kYW1sX3N1YnR5cGUgLS0+CjwhLS0gYGBgIC0tPgoKIyMjIENlbGwgdHlwZSBjb21wb3NpdGlvbgoKQ2VsbCB0eXBlcyBwcmVzZW50IGluIGRhdGEuCgpgYGB7cn0KdW5pcXVlKGdhbGVuX2FtbF9kb25vcnMkQ2VsbFR5cGUpCmBgYAoKTmV3IG1ldGFkYXRhIGNvbHVtbiB3aGV0aGVyIGNlbGwgaXMgYSBjYW5jZXIgY2VsbHR5cGUgb3IgaGVhbHRoeS4KCkNvbG9yIHNjaGVtZSB0byBhdm9pZCByYWluYm93IGNvbG9ycy4KCmBgYHtyfQpjZWxsX3R5cGVzIDwtIHVuaXF1ZShnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSRDZWxsVHlwZSkKY2VsbF90eXBlX2NvbG9ycyA8LQogIHNhbXBsZShjaGFtZWxlb246OmRpc3RpbmN0X2NvbG9ycyhuID0gbGVuZ3RoKGNlbGxfdHlwZXMpKSRuYW1lLAogICAgICAgICBzaXplID0gbGVuZ3RoKGNlbGxfdHlwZXMpKQpjZWxsX3R5cGVfY29sb3JzIDwtIHNldE5hbWVzKGNlbGxfdHlwZV9jb2xvcnMsIGNlbGxfdHlwZXMpCmBgYAoKQ3JlYXRlIG1vcmUgImNvYXJzZSIgY2VsbCB0eXBlIGxhYmVscy4gCkx5bXBob2lkIGFuZCBlcnl0aHJvaWQgKG5vbi1tYWxpZ25hbnQgb25seSksIGFuZCBNeWVsb2lkL0hTQywgTXllbG9pZC9IU0MtbGlrZS4gCgpgYGB7cn0KbHltcGhvaWRfY2VsbHR5cGVzIDwtIGMoIlBsYXNtYSIsCiAgICAgICAgICAgICAgICAgICAgICAgICJOSyIsCiAgICAgICAgICAgICAgICAgICAgICAgICJQcm9CIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkIiLAogICAgICAgICAgICAgICAgICAgICAgICAiVCIsCiAgICAgICAgICAgICAgICAgICAgICAgICJDVEwiLAogICAgICAgICAgICAgICAgICAgICAgICAicERDIikKYGBgCgpgYGB7cn0KZ2FsZW5fYW1sX2Rvbm9yc0BtZXRhLmRhdGEgPC0gZ2FsZW5fYW1sX2Rvbm9yc0BtZXRhLmRhdGEgJT4lCiAgbXV0YXRlKElzQ2FuY2VyQ2VsbHR5cGUgPSBpZmVsc2UoZ3JlcGwoIi1saWtlIiwgQ2VsbFR5cGUpLCBULCBGKSkgJT4lCiAgbXV0YXRlKENlbGxUeXBlR2VuZXJhbCA9IGlmZWxzZSgKICAgIENlbGxUeXBlICVpbiUgbHltcGhvaWRfY2VsbHR5cGVzLAogICAgIkx5bXBob2lkIiwKICAgIGlmZWxzZSgKICAgICAgSXNDYW5jZXJDZWxsdHlwZSwKICAgICAgIk15ZWxvaWQvSFNQQy1saWtlIiwKICAgICAgaWZlbHNlKGdyZXBsKCJFcnkiLCBDZWxsVHlwZSksICJFcnl0aHJvaWQiLCAiTXllbG9pZC9IU1BDIikKICAgICkKICApKQpgYGAKClZpc3VhbGl6ZSBjZWxsIHR5cGUgcHJvcG9ydGlvbnMgYWNyb3NzIHNhbXBsZXMuIAoKCmBgYHtyIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTh9CmdhbGVuX2FtbF9kb25vcnNAbWV0YS5kYXRhICU+JQogIGdyb3VwX2J5KG9yaWcuaWRlbnQpICU+JQogIGNvdW50KENlbGxUeXBlKSAlPiUKICBnZ3Bsb3QoYWVzKGZpbGwgPSBDZWxsVHlwZSwgeSA9IG4sIHggPSBvcmlnLmlkZW50KSkgKwogIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiLCBzdGF0ID0gImlkZW50aXR5IikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNlbGxfdHlwZV9jb2xvcnNbdW5pcXVlKGdhbGVuX2FtbF9kb25vcnNAbWV0YS5kYXRhJENlbGxUeXBlKV0pICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICAgICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkKCmdhbGVuX2FtbF9kb25vcnNAbWV0YS5kYXRhICU+JQogIGdyb3VwX2J5KG9yaWcuaWRlbnQpICU+JQogIGNvdW50KElzQ2FuY2VyQ2VsbHR5cGUpICU+JQogIGdncGxvdChhZXMoZmlsbCA9IElzQ2FuY2VyQ2VsbHR5cGUsIHkgPSBuLCB4ID0gb3JpZy5pZGVudCkpICsKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIiwgc3RhdCA9ICJpZGVudGl0eSIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICAgICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkKCmdhbGVuX2FtbF9kb25vcnNAbWV0YS5kYXRhICU+JQogIGdyb3VwX2J5KG9yaWcuaWRlbnQpICU+JQogIGNvdW50KFByZWRpY3Rpb25SZWZpbmVkKSAlPiUKICBnZ3Bsb3QoYWVzKGZpbGwgPSBQcmVkaWN0aW9uUmVmaW5lZCwgeSA9IG4sIHggPSBvcmlnLmlkZW50KSkgKwogIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiLCBzdGF0ID0gImlkZW50aXR5IikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwKICAgICAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKQoKZ2FsZW5fYW1sX2Rvbm9yc0BtZXRhLmRhdGEgJT4lCiAgZ3JvdXBfYnkob3JpZy5pZGVudCkgJT4lCiAgY291bnQoQ2VsbFR5cGVHZW5lcmFsKSAlPiUKICBnZ3Bsb3QoYWVzKGZpbGwgPSBDZWxsVHlwZUdlbmVyYWwsIHkgPSBuLCB4ID0gb3JpZy5pZGVudCkpICsKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIiwgc3RhdCA9ICJpZGVudGl0eSIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICAgICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgpNeWVsb2lkIGFuZCBIU1BDcyBhcmUgdGhlIGNlbGxzIHRoYXQgYXJlIGRpZmZpY3VsdCB0byBkaXN0aW5ndWlzaCBiZXR3ZWVuIGhlYWx0aHkgYW5kIG1hbGlnbmFudCBpbiBBTUwgY2VsbHMuIApUaGlzIGlzIHRoZSBhY3R1YWwgZGlmZmljdWx0IHByZWRpY3Rpb24gdGFzayBvZiB0aGUgY2xhc3NpZmllci4gClZpc3VhbGl6ZSB0aGUgcHJvcG9ydGlvbiBvZiB0aGVzZSBjZWxscyBpbiB0aGUgc2FtcGxlcy4gCgpgYGB7ciBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD04fQpnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSAlPiUKICBmaWx0ZXIoQ2VsbFR5cGVHZW5lcmFsICVpbiUgYygiTXllbG9pZC9IU1BDIiwgIk15ZWxvaWQvSFNQQy1saWtlIikpICU+JQogIGdyb3VwX2J5KG9yaWcuaWRlbnQsIENlbGxUeXBlR2VuZXJhbCkgJT4lCiAgc3VtbWFyaXplKG5fY2VsbHMgPSBuKCkpICU+JQogIGdncGxvdChhZXMoeCA9IG9yaWcuaWRlbnQsIHkgPSBuX2NlbGxzLCBmaWxsID0gQ2VsbFR5cGVHZW5lcmFsKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9ICJmaWxsIikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwKICAgICAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKQpgYGAKClByb3BvcnRpb24gb2YgY3ljbGluZyBjZWxscyBpcyBub3QgaGlnaGVyIGluIEFNTCBzYW1wbGVzIGF0IGRpYWdub3NpcyB0aGFuIGluIGhlYWx0aHkgc2FtcGxlcy4KQU1MIGlzIGEgZGlzZWFzZSBvZiBkaWZmZXJlbnRpYXRpb24uIApUaGUgYWNjdW11bGF0aW9uIG9mIGJsYXN0cyBpbiB0aGUgYm9uZSBtYXJyb3cgaXMgdGhlIHByb2JsZW0sIG5vdCBoeXBlciBwcm9saWZlcmF0aW9uLCBhcyBpbiBvdGhlciBjYW5jZXJzLiAKCmBgYHtyIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTh9CmdhbGVuX2FtbF9kb25vcnNAbWV0YS5kYXRhICU+JQogIGZpbHRlcihgRGF5cyBmcm9tIGRpYWdub3Npc2AgPT0gIkQwIiB8CiAgICAgICAgICAgU2FtcGxlQ2xhc3MgPT0gIkhlYWx0aHlEb25vciIpICU+JQogIGdyb3VwX2J5KG9yaWcuaWRlbnQsIFNhbXBsZUNsYXNzKSAlPiUKICBjb3VudChDeWNsaW5nQmluYXJ5KSAlPiUKICBnZ3Bsb3QoYWVzKGZpbGwgPSBDeWNsaW5nQmluYXJ5LCB5ID0gbiwgeCA9IG9yaWcuaWRlbnQpKSArCiAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIsIHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgZmFjZXRfZ3JpZCggfiBTYW1wbGVDbGFzcywgc2NhbGVzID0gImZyZWVfeCIsIHNwYWNlID0gImZyZWUiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLCBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCgojIyMgR2Vub3R5cGUgaW5mb3JtYXRpb24KCkNsYXNzaWZ5IGNlbGxzIGJhc2VkIG9uIHRoZWlyIGdlbm90eXBlIGluZm9ybWF0aW9uIGludG8gbWFsaWduYW50IGFuZCBoZWFsdGh5IGNlbGxzLiAKCkkgY2xhc3NpZnkgYWxsIGNlbGxzIHdpdGggYSBtdXRhbnQgdHJhbnNjcmlwdCBkZXRlY3RlZCBhcyBtYWxpZ25hbnQgY2VsbHMuICAKQWxsIG90aGVyIGNlbGxzIGZyb20gQU1MIHNhbXBsZXMgd2l0aCBhIHd0IGFsbGVsZSBkZXRlY3RlZCBjb3VsZCBiZSBjbGFzc2lmaWVkIGFzIGhlYWx0aHkuICAKSG93ZXZlciwgdGhpcyBpcyBpbXBlcmZlY3QsIGVzcGVjaWFsbHkgZm9yIGhldGVyb3p5Z291cyBkb21pbmFudCBuZWdhdGl2ZSBtdXRhdGlvbnMuICAKRnVydGhlcm1vcmUsIGR1ZSB0byBjbG9uYWxpdHksIHRoZXJlIG1pZ2h0IGJlIGNlbGxzIHdpdGggd3QgYWxsZWxlIGRldGVjdGVkIGFuZCBtdXQgYWxsZWxlIG1pc3NlZC4gIApCb3RoIGNhbiBsZWFkIHRvIGZhbHNlIHBvc2l0aXZlIGhlYWx0aHkgY2VsbHMuIAoKVGhlcmVmb3JlLCBvbmx5IHVzaW5nIGNlbGxzIGZyb20gQU1MIGRvbm9ycyBnZW5vdHlwZWQgYXMgbXV0YW50IGFuZCBoZWFsdGh5IGNlbGxzIGZyb20gaGVhbHRoeSBkb25vcnMgaW4gdGhlIGNsYXNzaWZpZXIgKGFzIGRvbmUgYnkgdmFuIEdhbGVuKS4KCiJUaGUgc2Vjb25kIGNsYXNzaWZpZXIgaXMgdXNlZCBmb3IgZGV0ZXJtaW5pbmcgaWYgYSBjZWxsIGZvciB3aGljaCB3ZSBkaWQgbm90IGRldGVjdCBhIG11dGFudCB0cmFuc2NyaXB0IGlzIG1hbGlnbmFudCBvciBub3JtYWwsIGJhc2VkIG9uIGl0cyBzaW1pbGFyaXR5IHRvIG5vcm1hbCBhbmQgbWFsaWduYW50IGNlbGxzIChpLmUuLCBjZWxscyBmcm9tIGhlYWx0aHkgQk0gYW5kIEhTQyB0byBteWVsb2lkLWxpa2UgY2VsbHMgZnJvbSB0dW1vciBzYW1wbGVzIGZvciB3aGljaCB3ZSBkZXRlY3RlZCBtdXRhbnQgdHJhbnNjcmlwdHMpLiIKCmBgYHtyfQpnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSA8LSBnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSAlPiUKICBtdXRhdGUoZ2Vub3R5cGUgPSBpZmVsc2UoCiAgICAhaXMubmEoTXV0VHJhbnNjcmlwdHMpICYKICAgICAgTXV0VHJhbnNjcmlwdHMgIT0gIiIsCiAgICAibWFsaWduYW50IiwKICAgIGlmZWxzZSgKICAgICAgIWlzLm5hKFd0VHJhbnNjcmlwdHMpICYKICAgICAgICBXdFRyYW5zY3JpcHRzICE9ICIiLAogICAgICAiaGVhbHRoeSIsCiAgICAgICJub3RfZGV0ZWN0ZWQiCiAgICApCiAgKSkKYGBgCgpQbG90IDE6IENlbGxzIHdpdGggbXV0IGRldGVjdGVkLCBvbmx5IHd0IGRldGVjdGVkIG9yIG5vIGNvdmVyYWdlLCBjb21wYXJlZCB0byBleHBlY3RlZCBibGFzdCBjb3VudC4KClBsb3QgMjogQ2VsbHMgdXNhYmxlIHRvIHRyaWFuIG1hbGlnbmFudCB2cy4gaGVhbHRoeSBjbGFzc2lmaWVyLiAKClRoZXJlIGFyZSBtdWx0aXBsZSBzYW1wbGVzIGFuZCBwYXRpZW50cyB3aXRoIG5vIG9yIGFsbW9zdCBubyBtYWxpZ25hbnQgZ2Vub3R5cGVkIGNlbGxzIHRoYXQgY2FuIGJlIHVzZWQgZm9yIHRyYWluaW5nLiAgClRoZXJlZm9yZSwgdGhlIGNsYXNzaWZpZXIgbmVlZHMgdG8gYmUgZ2VuZXJhbGl6YWJsZSB0byBvdGhlciBzYW1wbGVzIGFuZCBtdXRhdGlvbnMuICAKT3ZlcmZpdHRpbmcgdG8gc3BlY2lmaWMgcGF0aWVudHMgd291bGQgYmUgYmFkLgoKYGBge3J9CnAxIDwtIGdhbGVuX2FtbF9kb25vcnNAbWV0YS5kYXRhICU+JQogIGdyb3VwX2J5KG9yaWcuaWRlbnQsIGdlbm90eXBlKSAlPiUKICBzdW1tYXJpc2Uobl9jZWxscyA9IG4oKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGdncGxvdChhZXMoZmlsbCA9IGdlbm90eXBlLCB5ID0gbl9jZWxscywgeCA9IG9yaWcuaWRlbnQpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICB0aGVtZV9idygpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShkcm9wID0gRkFMU0UpICsKICB4bGFiKGVsZW1lbnRfYmxhbmsoKSkKCnAyIDwtIGdhbGVuX2FtbF9kb25vcnNAbWV0YS5kYXRhICU+JQogIHNlbGVjdChgQmxhc3QgY291bnRgLCBvcmlnLmlkZW50KSAlPiUKICB1bmlxdWUoKSAlPiUKICBnZ3Bsb3QoYWVzKHkgPSBgQmxhc3QgY291bnRgLCB4ID0gb3JpZy5pZGVudCkpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogIHRoZW1lX2J3KCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICAgICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHNjYWxlX3hfZGlzY3JldGUoZHJvcCA9IEZBTFNFKSArCiAgeGxhYihlbGVtZW50X2JsYW5rKCkpCgpnZ3B1YnI6OmdnYXJyYW5nZSgKICBwMSwKICBwMiwKICBhbGlnbiA9ICJ2IiwKICBoZWlnaHRzID0gYygyLCAxKSwKICBuY29sID0gMSwKICBucm93ID0gMgopCgpwMSA8LSBnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSAlPiUKICBtdXRhdGUoU2FtcGxlID0gYXMuZmFjdG9yKFNhbXBsZSkpICU+JQogIGZpbHRlcihnZW5vdHlwZSA9PSAibWFsaWduYW50IiB8CiAgICAgICAgICAgU2FtcGxlQ2xhc3MgPT0gIkhlYWx0aHlEb25vciIpICU+JQogIGdyb3VwX2J5KFNhbXBsZSwgZ2Vub3R5cGUpICU+JQogIHN1bW1hcmlzZShuX2NlbGxzID0gbigpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZ2dwbG90KGFlcyhmaWxsID0gZ2Vub3R5cGUsIHkgPSBuX2NlbGxzLCB4ID0gU2FtcGxlKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgdGhlbWVfYncoKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShkcm9wID0gRkFMU0UpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpICsgc2NhbGVfeF9kaXNjcmV0ZShkcm9wID0gRkFMU0UpCgpwMiA8LSBnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSAlPiUKICBmaWx0ZXIoZ2Vub3R5cGUgPT0gIm1hbGlnbmFudCIgfAogICAgICAgICAgIFNhbXBsZUNsYXNzID09ICJIZWFsdGh5RG9ub3IiKSAlPiUKICBncm91cF9ieShvcmlnLmlkZW50LCBnZW5vdHlwZSkgJT4lCiAgc3VtbWFyaXNlKG5fY2VsbHMgPSBuKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBnZ3Bsb3QoYWVzKGZpbGwgPSBnZW5vdHlwZSwgeSA9IG5fY2VsbHMsIHggPSBvcmlnLmlkZW50KSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgdGhlbWVfYncoKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShkcm9wID0gRkFMU0UpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpICsgc2NhbGVfeF9kaXNjcmV0ZShkcm9wID0gRkFMU0UpCgpnZ3B1YnI6OmdnYXJyYW5nZSgKICBwMSwKICBwMiwKICBhbGlnbiA9ICJ2IiwKICBoZWlnaHRzID0gYygxLCAxKSwKICBuY29sID0gMSwKICBucm93ID0gMgopCmBgYAoKQ29uY29yZGFuY2Ugb2YgZ2Vub3R5cGUgYW5kIHZhbiBHYWxlbiBjZWxsdHlwZSBhbm5vdGF0aW9uIGluIEFNTCBzYW1wbGVzLiAKTkI6IENlbGxzIHdpdGggb25seSB3dCB0cmFuc2NyaXB0IGRldGVjdGVkICgiaGVhbHRoeSIpIGluIEFNTCBzYW1wbGVzIG1pZ2h0IG5vdCBiZSBhY3R1YWxseSBoZWFsdGh5IChoZXRlcm96eWdvc2l0eSwgY2xvbmFsaXR5KS4gCgpgYGB7cn0KZ2FsZW5fYW1sX2Rvbm9yc19tZXRhZGF0YSA8LSBnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YQp0YWJsZSgKICAgIGZpbHRlcihnYWxlbl9hbWxfZG9ub3JzX21ldGFkYXRhLCBTYW1wbGVDbGFzcyAhPSAiSGVhbHRoeURvbm9yIikkZ2Vub3R5cGUsCiAgICBmaWx0ZXIoZ2FsZW5fYW1sX2Rvbm9yc19tZXRhZGF0YSwgU2FtcGxlQ2xhc3MgIT0gIkhlYWx0aHlEb25vciIpJENlbGxUeXBlCiAgKQpgYGAKCgo8IS0tIEhlYXRtYXAgb2YgbnVtYmVyIG9mIGNlbGxzIGZyb20gQU1MIGRvbm9ycyAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIGdhbGVuX2FtbF9kb25vcnNfbWV0YWRhdGEgPC0gZ2FsZW5fYW1sX2Rvbm9yc0BtZXRhLmRhdGEgLS0+CjwhLS0gaGVhdG1hcCggLS0+CjwhLS0gICB0YWJsZSggLS0+CjwhLS0gICAgIGZpbHRlcihnYWxlbl9hbWxfZG9ub3JzX21ldGFkYXRhLCBTYW1wbGVDbGFzcyAhPSAiSGVhbHRoeURvbm9yIikkZ2Vub3R5cGUsIC0tPgo8IS0tICAgICBmaWx0ZXIoZ2FsZW5fYW1sX2Rvbm9yc19tZXRhZGF0YSwgU2FtcGxlQ2xhc3MgIT0gIkhlYWx0aHlEb25vciIpJENlbGxUeXBlIC0tPgo8IS0tICAgKSwgLS0+CjwhLS0gICBzY2FsZSA9IE5VTEwsIC0tPgo8IS0tICAgQ29sdiA9IE5BLCAtLT4KPCEtLSAgIFJvd3YgPSBOQSAtLT4KPCEtLSApIC0tPgo8IS0tIGBgYCAtLT4KCgoKU2V4IGJpYXMgaW4gY2VsbHMgdGhhdCBjYW4gYmUgdXNlZCB0byB0cmFpbiBjbGFzc2lmaWVyIChtdXQgdHJhbnNjcmlwdCBkZXRlY3RlZCBmcm9tIEFNTCBkb25vciwgb3IgZnJvbSBoZWFsdGh5IGRvbm9yKS4gCgpgYGB7ciBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD02fQpwMSA8LSBnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSAlPiUKICBmaWx0ZXIoZ2Vub3R5cGUgPT0gIm1hbGlnbmFudCIgfAogICAgICAgICAgIFNhbXBsZUNsYXNzID09ICJIZWFsdGh5RG9ub3IiKSAlPiUKICBtdXRhdGUoY2xhc3MgPSBpZmVsc2UoZ2Vub3R5cGUgPT0gIm1hbGlnbmFudCIsICJtYWxpZ25hbnQiLCAiaGVhbHRoeSIpKSAlPiUKICBncm91cF9ieShHZW5kZXIsIGNsYXNzKSAlPiUKICBzdW1tYXJpc2Uobl9jZWxscyA9IG4oKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGdncGxvdChhZXMoeCA9IGNsYXNzLCB5ID0gbl9jZWxscywgZmlsbCA9IEdlbmRlcikpICsKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIiwgc3RhdCA9ICJpZGVudGl0eSIpICsKICAjIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpCgpwMiA8LSBnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSAlPiUKICBmaWx0ZXIoZ2Vub3R5cGUgPT0gIm1hbGlnbmFudCIgfAogICAgICAgICAgIFNhbXBsZUNsYXNzID09ICJIZWFsdGh5RG9ub3IiKSAlPiUKICBtdXRhdGUoY2xhc3MgPSBpZmVsc2UoZ2Vub3R5cGUgPT0gIm1hbGlnbmFudCIsICJtYWxpZ25hbnQiLCAiaGVhbHRoeSIpKSAlPiUKICBncm91cF9ieShHZW5kZXIsIGNsYXNzKSAlPiUKICBzdW1tYXJpc2Uobl9jZWxscyA9IG4oKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGdncGxvdChhZXMoeCA9IEdlbmRlciwgeSA9IG5fY2VsbHMsIGZpbGwgPSBjbGFzcykpICsKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIiwgc3RhdCA9ICJpZGVudGl0eSIpICsKICAjIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpCgpwMSB8IHAyCmBgYAoKCiMjIyBBY2N1cmFjeSBvZiB0aGUgY2xhc3NpZmllcgoKQ2hlY2sgdGhlIGFjY3VyYWN5IG9mIHRoZSB2YW4gR2FsZW4gY2xhc3NpZmllciBiYXNlZCBvbiBjb25jb3JkYW5jZSB3aXRoIGNsaW5pY2FsIGJsYXN0IGNvdW50LiAKVGhpcyBoYXMgYWxyZWFkeSBiZWVuIGNoZWNrZWQgaW4gdGhlIHBhcGVyIGFuZCB0aGUgYW5zd2VyIGlzIHRoYXQgdGhlIGNvcnJlbGF0aW9uIGlzIGhpZ2guIAoKYGBge3J9CiMgc2FtcGxlcyB3aXRoIHVuY2xlYXIgcHJlZGljdGlvbgpnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSAlPiUKICBmaWx0ZXIoUHJlZGljdGlvblJlZmluZWQgPT0gInVuY2xlYXIiKSAlPiUKICBwdWxsKG9yaWcuaWRlbnQpICU+JQogIHVuaXF1ZSgpICU+JSBhcy5jaGFyYWN0ZXIoKQoKcHJlZF9tYWxpZ25hbnRfdnNfYmxhc3RfY291bnQgPC0gZ2FsZW5fYW1sX2Rvbm9yc0BtZXRhLmRhdGEgJT4lCiAgZ3JvdXBfYnkoCiAgICBvcmlnLmlkZW50LAogICAgUHJlZGljdGlvblJlZmluZWQsCiAgICBgQmxhc3QgY291bnRgLAogICAgYENlbGwgbnVtYmVyYCwKICAgIFNhbXBsZSwKICAgIEdlbmRlciwKICAgICMgRHJpdmVyTXV0YXRpb25zLAogICAgYERheXMgZnJvbSBkaWFnbm9zaXNgCiAgKSAlPiUKICBzdW1tYXJpemUoUHJlZGljdGlvblJlZmluZWRQcm9wb3J0aW9uID0gbigpIC8gYENlbGwgbnVtYmVyYCkgJT4lCiAgZmlsdGVyKFByZWRpY3Rpb25SZWZpbmVkID09ICJtYWxpZ25hbnQiKSAlPiUKICBzZWxlY3QoCiAgICBgQmxhc3QgY291bnRgLAogICAgUHJlZGljdGlvblJlZmluZWRQcm9wb3J0aW9uLAogICAgb3JpZy5pZGVudCwKICAgIFNhbXBsZSwKICAgIEdlbmRlciwKICAgICMgRHJpdmVyTXV0YXRpb25zLAogICAgYERheXMgZnJvbSBkaWFnbm9zaXNgCiAgKSAlPiUKICB1bmlxdWUoKSAlPiUKICBtdXRhdGUoYEJsYXN0IGNvdW50YCA9IGFzLm51bWVyaWMoYEJsYXN0IGNvdW50YCkpICU+JQogIHVuZ3JvdXAoKQoKbnJvdyhwcmVkX21hbGlnbmFudF92c19ibGFzdF9jb3VudCkKCnByZWRfbWFsaWduYW50X3ZzX2JsYXN0X2NvdW50ICU+JQogIGdncGxvdChhZXMoCiAgICB4ID0gUHJlZGljdGlvblJlZmluZWRQcm9wb3J0aW9uLAogICAgeSA9IGBCbGFzdCBjb3VudGAsCiAgICBncm91cCA9IFNhbXBsZSwKICAgIGNvbG9yID0gU2FtcGxlCiAgKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwKSArCiAgZ2dyZXBlbDo6Z2VvbV90ZXh0X3JlcGVsKGFlcyhsYWJlbCA9IG9yaWcuaWRlbnQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMywKICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4Lm92ZXJsYXBzID0gSW5mKSArCiAgdGhlbWVfYncoKSArCiAgY29vcmRfZXF1YWwoKSArCiAgdGhlbWUocGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgoKCmBgYHtyfQpjb3IudGVzdCgKICBwcmVkX21hbGlnbmFudF92c19ibGFzdF9jb3VudCRQcmVkaWN0aW9uUmVmaW5lZFByb3BvcnRpb24sCiAgcHJlZF9tYWxpZ25hbnRfdnNfYmxhc3RfY291bnQkYEJsYXN0IGNvdW50YAopCmBgYAoKSWYgdGhlIHNleCBiaWFzIGluIGhlYWx0aHkgYW5kIEFNTCBzYW1wbGVzIG1hdHRlcnMsIHRoZW4gdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBwcmVkaWN0ZWQgYW5kIG9ic2VydmVkIGJsYXN0IGNvdW50IChlcnJvcikgc2hvdWxkIGJlIGxhcmdlciBpbiBmZW1hbGVzIHRoYW4gbWFsZXMuIAoKUHJlZGljdGVkIHByb3BvcnRpb24gb2YgbWFsaWduYW50IGNlbGxzIGFncmVlcyBsZXNzIHdpdGggY2xpbmljYWwgYmxhc3QgY291bnQgZm9yIGZlbWFsZSBzYW1wbGVzLgpNYXJrZWRseSwgZmVtYWxlIHNhbXBsZXMgaGF2ZSBhbiBvdmVyZXN0aW1hdGVkIHByb3BvcnRpb24gb2YgbWFsaWduYW50IGNlbGwuIAoKSG93ZXZlciwgdGhpcyBjb3VsZCBhbHNvIGJlIGR1ZSB0byBvdGhlciBmYWN0b3JzIGNvdmFyeWluZyB3aXRoIFNleCwgbGlrZSBtdXRhdGVkIGdlbmUgb3IgYmxhc3QgY291bnQuCgpNZWRpYW4gYWJzb2x1dGUgZXJyb3IgMTAuOCUoRiksIDMuMSUoTSkuCgpgYGB7ciBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD00fQpwcmVkX21hbGlnbmFudF92c19ibGFzdF9jb3VudF9lcnJvciA8LSBwcmVkX21hbGlnbmFudF92c19ibGFzdF9jb3VudCAlPiUKICBtdXRhdGUoZXJyb3IgPSBQcmVkaWN0aW9uUmVmaW5lZFByb3BvcnRpb24gLSBgQmxhc3QgY291bnRgLAogICAgICAgICBhYnNvbHV0ZV9lcnJvciA9IGFicyhlcnJvciksCiAgICAgICAgIHJlbGF0aXZlX2Vycm9yID0gUHJlZGljdGlvblJlZmluZWRQcm9wb3J0aW9uIC8gYEJsYXN0IGNvdW50YCkgJT4lCiAgcGl2b3RfbG9uZ2VyKAogICAgY29scyA9IGMoZXJyb3IsIGFic29sdXRlX2Vycm9yLCByZWxhdGl2ZV9lcnJvciksCiAgICBuYW1lc190byA9ICJlcnJvcl90eXBlIiwKICAgIHZhbHVlc190byA9ICJlcnJvciIKICApICU+JQogIGdyb3VwX2J5KGVycm9yX3R5cGUsIEdlbmRlcikgCgpwcmVkX21hbGlnbmFudF92c19ibGFzdF9jb3VudF9lcnJvciAlPiUKICBzdW1tYXJpemUobWVkaWFuX2Vycm9yID0gcm91bmQobWVkaWFuKGVycm9yKSwgMykpCmBgYAoKCmBgYHtyIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTR9CnN5bW1ldHJpY19saW1pdHMgPC0gZnVuY3Rpb24gKHgpCnsKICBtYXggPC0gbWF4KGFicyh4KSkKICBjKC1tYXgsIG1heCkKfQoKcDEgPC0gcHJlZF9tYWxpZ25hbnRfdnNfYmxhc3RfY291bnRfZXJyb3IgJT4lCiAgZmlsdGVyKGVycm9yX3R5cGUgJWluJSBjKCJlcnJvciIsICJhYnNvbHV0ZV9lcnJvciIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBlcnJvcl90eXBlLCB5ID0gZXJyb3IgKiAxMDAsIGZpbGwgPSBHZW5kZXIpKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGUgPSAyKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2l6ZSA9IDApICsKICBnZW9tX3BvaW50KHBjaCA9IDIxLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcmRvZGdlKGppdHRlci53aWR0aCA9IDAuMSkpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdChzY2FsZSA9IDEpKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUocGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwgbGVnZW5kLnBvc2l0aW9uID0gIk5vbmUiKSArCiAgeWxhYigiUHJlZGljdGlvbiBlcnJvciAlIGJsYXN0cyIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gc3ltbWV0cmljX2xpbWl0cykgKwogIHhsYWIoZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkKCnAyIDwtIHByZWRfbWFsaWduYW50X3ZzX2JsYXN0X2NvdW50X2Vycm9yICU+JQogIGZpbHRlcihlcnJvcl90eXBlICVpbiUgYygicmVsYXRpdmVfZXJyb3IiKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZXJyb3JfdHlwZSwgeSA9IGxvZzIoZXJyb3IpLCBmaWxsID0gR2VuZGVyKSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gMikgKwogIGdlb21fYm94cGxvdChvdXRsaWVyLnNpemUgPSAwKSArCiAgZ2VvbV9wb2ludChwY2ggPSAyMSwgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXJkb2RnZShqaXR0ZXIud2lkdGggPSAwLjEpKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUocGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHlsYWIoImxvZzIgcmVsYXRpdmUgZXJyb3IgJSBibGFzdHMiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IHN5bW1ldHJpY19saW1pdHMpICsKICB4bGFiKGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCgpnZ3B1YnI6OmdnYXJyYW5nZShwMSwgcDIsIGFsaWduID0gImgiLCB3aWR0aHMgPSBjKDEsIDEuMikpCmBgYAoKCjwhLS0gUmVzaWR1YWxzIG9mIGxpbmVhciBtb2RlbC4gIC0tPgoKPCEtLSBgYGB7ciBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD0zLjV9IC0tPgo8IS0tIG1vZGVsIDwtIGxtKFByZWRpY3Rpb25SZWZpbmVkUHJvcG9ydGlvbiB+IGBCbGFzdCBjb3VudGAsIGRhdGE9cHJlZF9tYWxpZ25hbnRfdnNfYmxhc3RfY291bnQpICAtLT4KCjwhLS0gdG1wIDwtIHByZWRfbWFsaWduYW50X3ZzX2JsYXN0X2NvdW50ICU+JSAtLT4KPCEtLSAgIGRwbHlyOjptdXRhdGUoIC0tPgo8IS0tICAgICBmaXRzID0gZml0dGVkKG1vZGVsKSwgLS0+CjwhLS0gICAgIHJlc2lkcyA9IHJlc2lkKG1vZGVsKSwgLS0+CjwhLS0gICAgIHNyZXNpZHMgPSByc3R1ZGVudChtb2RlbCkgLS0+CjwhLS0gICApIC0tPgoKPCEtLSB0bXAgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkoR2VuZGVyKSAlPiUgLS0+CjwhLS0gICBzdW1tYXJpemUoc3VtKGFicyhyZXNpZHMpKSwgbWVhbihhYnMocmVzaWRzKSksIG1lZGlhbihhYnMocmVzaWRzKSkpIC0tPgoKPCEtLSAjY3JlYXRlIHJlc2lkdWFsIHBsb3QgLS0+CjwhLS0gZ2dwbG90KHRtcCwgYWVzKHggPSBmaXRzLCB5ID0gcmVzaWRzLCBjb2xvciA9IEdlbmRlcikpICsgLS0+CjwhLS0gICBnZ3JlcGVsOjpnZW9tX3RleHRfcmVwZWwoYWVzKGxhYmVsID0gcGFzdGUoU2FtcGxlLCAsIHNlcCA9ICJ8IikpLCBtYXgub3ZlcmxhcHMgPSBJbmYpICsgLS0+CjwhLS0gICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9IDIpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KCkgKyAtLT4KPCEtLSAgIHRoZW1lX2J3KCkgLS0+CjwhLS0gYGBgIC0tPgoKCgojIyMgRGF0YSBxdWFsaXR5CgpBY2NvcmRpbmcgdG8gbWFudXNjcmlwdCwgY2VsbHMgd2VyZSBmaWx0ZXJlZCBmb3IgPjEwMDAgVU1JLCA+NTAwIGdlbmVzIGFuZCA8MjAlIG1pdG9jaG9uZHJpYWwrcmlib3NvbWFsIHRyYW5zY3JpcHRzLgoKVGhlIFJkYXRhIG9iamVjdCBjb250YWlucyBmaWx0ZXJlZCBjZWxscy4gCgpgYGB7ciBmaWcud2lkdGg9MjAsIGZpZy5oZWlnaHQ9MTB9ClZsblBsb3QoZ2FsZW5fYW1sX2Rvbm9ycywgZmVhdHVyZXMgPSBjKCJuQ291bnRfUk5BIiwgIm5GZWF0dXJlX1JOQSIpLCBuY29sID0gMSwgbG9nID0gVCwgcHQuc2l6ZSA9IDApCmBgYAoKPCEtLSBgYGB7ciBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD01fSAtLT4KPCEtLSBnYWxlbl9hbWxfZG9ub3JzQG1ldGEuZGF0YSAlPiUgLS0+CjwhLS0gICBncm91cF9ieShvcmlnLmlkZW50LCBgQ2VsbCBudW1iZXJgLCBTYW1wbGVDbGFzcykgJT4lIC0tPgo8IS0tICAgZHBseXI6OnN1bW1hcmlzZShtZWRpYW5fbkZlYXR1cmVfUk5BID0gbWVkaWFuKG5GZWF0dXJlX1JOQSkpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoeCA9IGBDZWxsIG51bWJlcmAsIHkgPSBtZWRpYW5fbkZlYXR1cmVfUk5BKSkgKyAtLT4KPCEtLSAgIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gU2FtcGxlQ2xhc3MpKSArIC0tPgo8IS0tICAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikgKyAtLT4KPCEtLSAgIHRoZW1lX2J3KCkgKyAtLT4KPCEtLSAgIHRoZW1lKHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpIC0tPgo8IS0tIGBgYCAtLT4KCgojIyMgTG93IGRpbWVuc2lvbmFsIGVtYmVkZGluZwoKIyMjIyBBTUwgYW5kIGhlYWx0aHkgZG9ub3JzCgpOb3JtYWxpemUgZGF0YSBieSBsaWJyYXJ5IHNpemUgYW5kIGxvZyB0cmFuc2Zvcm0uICAKSWRlbnRpZnkgMjAwMCBoaWdobHkgdmFyaWFibGUgZ2VuZXMuICAKU2NhbGUvei1zY29yZSBoaWdobHkgdmFyaWFibGUgZ2VuZXMuICAKCmBgYHtyIHJlc3VsdHM9Rn0KZ2FsZW5fYW1sX2Rvbm9ycyA8LSBOb3JtYWxpemVEYXRhKGdhbGVuX2FtbF9kb25vcnMsIHNjYWxlLmZhY3RvciA9IDEwMDAwKQpnYWxlbl9hbWxfZG9ub3JzIDwtIEZpbmRWYXJpYWJsZUZlYXR1cmVzKGdhbGVuX2FtbF9kb25vcnMpCmdhbGVuX2FtbF9kb25vcnMgPC0gU2NhbGVEYXRhKGdhbGVuX2FtbF9kb25vcnMpCmBgYAoKQ2FsY3VsYXRlIFBDQSBhbmQgY2hlY2sgZWxib3cgcGxvdCBmb3IgYXBwcm9wcmlhdGUgbnVtYmVyIG9mIFBDcyBmb3IgVU1BUCBlbWJlZGRpbmcuIAoKYGBge3IgcmVzdWx0cz1GfQpnYWxlbl9hbWxfZG9ub3JzIDwtIFJ1blBDQShnYWxlbl9hbWxfZG9ub3JzKQpgYGAKCgpgYGB7cn0KRWxib3dQbG90KGdhbGVuX2FtbF9kb25vcnMsIG5kaW1zID0gNTApICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMCwgTkEpKQpgYGAKCkNhbGN1bGF0ZSBVTUFQIGVtYmVkZGluZy4gCgpgYGB7ciByZXN1bHRzPUZ9CmdhbGVuX2FtbF9kb25vcnMgPC0gUnVuVU1BUChnYWxlbl9hbWxfZG9ub3JzLCBkaW1zID0gMToxNSkKYGBgCgpDaGVjayBVTUFQIGZvciB0ZWNobmljYWwgYW5kIGJpb2xvZ2ljYWwgdmFyaWF0aW9uLgoKSGlnaGVyIHJlYWQgY291bnRzIGluIGVyeXRocm9jeXRlIHByb2dlbml0b3JzLgoKCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTd9CiMgdGVjaG5pY2FsCkZlYXR1cmVQbG90KGdhbGVuX2FtbF9kb25vcnMsIGZlYXR1cmVzID0gIm5Db3VudF9STkEiLCBwdC5zaXplID0gMC4wMDUsIG1heC5jdXRvZmYgPSAicTk5IikgKyBjb29yZF9lcXVhbCgpCkZlYXR1cmVQbG90KGdhbGVuX2FtbF9kb25vcnMsIGZlYXR1cmVzID0gIm5GZWF0dXJlX1JOQSIsIHB0LnNpemUgPSAwLjAwNSkgKyBjb29yZF9lcXVhbCgpCmBgYAoKCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTd9CiMgYW5ub3RhdGlvbiBiaW9sb2dpY2FsCkRpbVBsb3QoZ2FsZW5fYW1sX2Rvbm9ycywgZ3JvdXAuYnkgPSAiU2FtcGxlQ2xhc3MiLCBzaHVmZmxlID0gVCwgcHQuc2l6ZSA9IDAuMDA1KSArIGNvb3JkX2VxdWFsKCkKRGltUGxvdChnYWxlbl9hbWxfZG9ub3JzLCBncm91cC5ieSA9ICJJc0NhbmNlckNlbGx0eXBlIiwgc2h1ZmZsZSA9IFQsIHB0LnNpemUgPSAwLjAwNSkgKyBjb29yZF9lcXVhbCgpCkRpbVBsb3QoZ2FsZW5fYW1sX2Rvbm9ycywgZ3JvdXAuYnkgPSAiUHJlZGljdGlvblJlZmluZWQiLCBzaHVmZmxlID0gVCwgcHQuc2l6ZSA9IDAuMDA1KSArIGNvb3JkX2VxdWFsKCkKRGltUGxvdChnYWxlbl9hbWxfZG9ub3JzLCBncm91cC5ieSA9ICJDZWxsVHlwZSIsIHNodWZmbGUgPSBULCBwdC5zaXplID0gMC4wMDUsIGxhYmVsID0gVCwgbGFiZWwuc2l6ZSA9IDMpICsgY29vcmRfZXF1YWwoKQpEaW1QbG90KGdhbGVuX2FtbF9kb25vcnMsIGdyb3VwLmJ5ID0gIm9yaWcuaWRlbnQiLCBzaHVmZmxlID0gVCwgcHQuc2l6ZSA9IDAuMDA1LCBsYWJlbCA9IFQsIGxhYmVsLnNpemUgPSAyKSArIGNvb3JkX2VxdWFsKCkgKyBOb0xlZ2VuZCgpCkZlYXR1cmVQbG90KGdhbGVuX2FtbF9kb25vcnMsIGZlYXR1cmVzID0gIkFnZSIsIHB0LnNpemUgPSAwLjAwNSkgKyBjb29yZF9lcXVhbCgpCkRpbVBsb3Qoc3Vic2V0KGdhbGVuX2FtbF9kb25vcnMsIFNhbXBsZUNsYXNzID09ICJIZWFsdGh5RG9ub3IiKSwgZ3JvdXAuYnkgPSAib3JpZy5pZGVudCIsIHNodWZmbGUgPSBULCBwdC5zaXplID0gMC4wMDUpICsgY29vcmRfZXF1YWwoKQpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD03fQojIHNleCBkaWZmZXJlbmNlcwpEaW1QbG90KGdhbGVuX2FtbF9kb25vcnMsIGdyb3VwLmJ5ID0gIkdlbmRlciIsIHNodWZmbGUgPSBULCBwdC5zaXplID0gMC4wMDUpICsgY29vcmRfZXF1YWwoKQpEaW1QbG90KHN1YnNldChnYWxlbl9hbWxfZG9ub3JzLCBTYW1wbGVDbGFzcyA9PSAiQW1sRG9ub3IiKSwgZ3JvdXAuYnkgPSAiR2VuZGVyIiwgc2h1ZmZsZSA9IFQsIHB0LnNpemUgPSAwLjAwNSkgKyBjb29yZF9lcXVhbCgpCkRpbVBsb3Qoc3Vic2V0KGdhbGVuX2FtbF9kb25vcnMsIFNhbXBsZUNsYXNzID09ICJBbWxEb25vciIpLCBncm91cC5ieSA9ICJTYW1wbGUiLCBzaHVmZmxlID0gVCwgcHQuc2l6ZSA9IDAuMDA1LCBzcGxpdC5ieSA9ICJHZW5kZXIiKSArIGNvb3JkX2VxdWFsKCkKRGltUGxvdChnYWxlbl9hbWxfZG9ub3JzLCBncm91cC5ieSA9ICJTYW1wbGUiLCBzaHVmZmxlID0gVCwgcHQuc2l6ZSA9IDAuMDA1LCBzcGxpdC5ieSA9ICJHZW5kZXIiKSArIGNvb3JkX2VxdWFsKCkKYGBgCgojIyMjIEVtYmVkZGluZyBoZWFsdGh5IGRvbm9ycwoKQ3JlYXRlIGFuIGVtYmVkZGluZyBvZiBqdXN0IHRoZSBoZWFsdGh5IHNhbXBsZXMgdG8gdmlzdWFsaXplIHRoZSBub3JtYWwgaGVtYXRvcG9pZXRpYyB0cmFqZWN0b3J5LiAgClNhbWUgcHJvY2VkdXJlIGFzIGFib3ZlLiAKCmBgYHtyfQpnYWxlbl9hbWxfZG9ub3JzX2hlYWx0aHkgPC0gc3Vic2V0KGdhbGVuX2FtbF9kb25vcnMsIFNhbXBsZUNsYXNzID09ICJIZWFsdGh5RG9ub3IiKQpgYGAKCmBgYHtyIHJlc3VsdHM9Rn0KZ2FsZW5fYW1sX2Rvbm9yc19oZWFsdGh5IDwtIEZpbmRWYXJpYWJsZUZlYXR1cmVzKGdhbGVuX2FtbF9kb25vcnNfaGVhbHRoeSkKZ2FsZW5fYW1sX2Rvbm9yc19oZWFsdGh5IDwtIFNjYWxlRGF0YShnYWxlbl9hbWxfZG9ub3JzX2hlYWx0aHkpCmBgYAoKYGBge3IgcmVzdWx0cz1GfQpnYWxlbl9hbWxfZG9ub3JzX2hlYWx0aHkgPC0gUnVuUENBKGdhbGVuX2FtbF9kb25vcnNfaGVhbHRoeSkKYGBgCgpgYGB7cn0KRWxib3dQbG90KGdhbGVuX2FtbF9kb25vcnNfaGVhbHRoeSwgbmRpbXMgPSA1MCkgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLCBOQSkpCmBgYAoKYGBge3IgcmVzdWx0cz1GfQpnYWxlbl9hbWxfZG9ub3JzX2hlYWx0aHkgPC0gUnVuVU1BUChnYWxlbl9hbWxfZG9ub3JzX2hlYWx0aHksIGRpbXMgPSAxOjE1KQpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD03fQpEaW1QbG90KGdhbGVuX2FtbF9kb25vcnNfaGVhbHRoeSwgZ3JvdXAuYnkgPSAiQ2VsbFR5cGUiLCBzaHVmZmxlID0gVCwgcHQuc2l6ZSA9IDAuMDEsIGxhYmVsID0gVCwgbGFiZWwuc2l6ZSA9IDMpICsKICBjb29yZF9lcXVhbCgpCkRpbVBsb3QoZ2FsZW5fYW1sX2Rvbm9yc19oZWFsdGh5LCBncm91cC5ieSA9ICJvcmlnLmlkZW50Iiwgc2h1ZmZsZSA9IFQsIHB0LnNpemUgPSAwLjAxLCBsYWJlbCA9IFQsIGxhYmVsLnNpemUgPSAzKSArCiAgY29vcmRfZXF1YWwoKSArIE5vTGVnZW5kKCkKYGBgCgoKPCEtLSBDaGVjayBob3cgcmVsaWFibGUgVU1BUCBpcywgaS5lLiBob3cgd2VsbCBpdCBjYXB0dXJlcyB0aGUgUEMgc3BhY2UsIHVzaW5nIHRoZSBkZWZhdWx0IFVNQVAgcGFyYW1ldGVycy4gLS0+Cgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBsaWJyYXJ5KHNjREVFRCkgLS0+CjwhLS0gSyA9IDggLS0+CjwhLS0gcmVzdWx0ID0gc2NERUVEKGdhbGVuX2FtbF9kb25vcnNfaGVhbHRoeSwgSyA9IEssIHJlZHVjdGlvbi5tZXRob2QgPSAndW1hcCcsIG5fbmVpZ2hib3JzID0gMzAsIG1pbi5kaXN0ID0gMC4zLCByZXJ1biA9IEYpIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIGR1YmlvdXNfY2VsbHMgPSByZXN1bHQkZnVsbF9yZXN1bHRzJGR1YmlvdXNfY2VsbHNbcmVzdWx0JGZ1bGxfcmVzdWx0cyRuX25laWdoYm9ycyA9PSAnMzAnXSAtLT4KPCEtLSBkdWJpb3VzX2NlbGxzID0gYXMubnVtZXJpYyhzdHJzcGxpdChkdWJpb3VzX2NlbGxzLCAnLCcpW1sxXV0pIC0tPgo8IS0tIHRydXN0d29ydGh5X2NlbGxzID0gIHJlc3VsdCRmdWxsX3Jlc3VsdHMkdHJ1c3R3b3J0aHlfY2VsbHNbcmVzdWx0JGZ1bGxfcmVzdWx0cyRuX25laWdoYm9ycyA9PSAnMzAnXSAtLT4KPCEtLSB0cnVzdHdvcnRoeV9jZWxscyA9IGFzLm51bWVyaWMoc3Ryc3BsaXQodHJ1c3R3b3J0aHlfY2VsbHMsICcsJylbWzFdXSkgLS0+Cgo8IS0tIERpbVBsb3QoIC0tPgo8IS0tICAgZ2FsZW5fYW1sX2Rvbm9yc19oZWFsdGh5LCAtLT4KPCEtLSAgIHJlZHVjdGlvbiA9ICd1bWFwJywgLS0+CjwhLS0gICBjZWxscy5oaWdobGlnaHQgPSBsaXN0KCdkdWJpb3VzJyA9IGR1YmlvdXNfY2VsbHMsICd0cnVzdHdvcnRoeScgPSB0cnVzdHdvcnRoeV9jZWxscykgLS0+CjwhLS0gKSArIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCdncmF5JywgJ2JsdWUnLCAncmVkJykpICsgY29vcmRfZXF1YWwoKSAtLT4KPCEtLSBgYGAgLS0+CgojIyMjIEFNTCBhbmQgaGVhbHRoeSBkb25vcnMgZXhjbHVkaW5nIHNleC1zcGVjaWZpYyBnZW5lcwoKU0RFIHNjb3JlcyBmb3IgYWxsIHByb3RlaW4tY29kaW5nIGdlbmVzIGluIDQ1IHRpc3N1ZXMgY29tbW9uIHRvIG1lbiBhbmQgd29tZW4uIApHZW5lcyB3ZXJlIGFuYWx5emVkIGJ5IE5PSVNlcUJJTyB3aXRoIHNjb3JlcyBvZiB6ZXJvIGdpdmVuIGZvciBnZW5lcyB3aXRoIGluc2lnbmlmaWNhbnQgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24uIApPdGhlciBnZW5lcyBoYXZlIFNERSBzY29yZXMgYmVsb3cgemVybyBmb3IgbWVuLWJpYXNlZCBleHByZXNzaW9uIGFuZCBhYm92ZSB6ZXJvIGZvciB3b21lbi1iaWFzZWQgZXhwcmVzc2lvbi4gKENTViAyMjA1IGtiKQoKQm9uZSBtYXJyb3cgaXMgbm90IGF2YWlsYWJsZS4gSW5zdGVhZCB1c2Ugd2hvbGUgYmxvb2QgYW5kIHNwbGVlbi4KCmBgYHtyfQpzZXhfZ2VuZXMgPC0gcmVhZF9jc3YoImdlcnNob25pX2V0X2FsLzEyOTE1XzIwMTdfMzUyX01PRVNNM19FU00uY3N2IikKc2V4X2dlbmVzIDwtIHNleF9nZW5lcyAlPiUgCiAgc2VsZWN0KEdlbmUsIFNwbGVlbiwgV2hvbGVfQmxvb2QpICU+JQogIGZpbHRlcihTcGxlZW4gIT0gMCB8IFdob2xlX0Jsb29kICE9IDApICU+JQogIG11dGF0ZShnZW5lID0gZ3N1YigiXy4qIiwgIiIsIEdlbmUpKSAlPiUKICBwdWxsKGdlbmUpCgpzZXhfZ2VuZXMgPC0gYyhzZXhfZ2VuZXMsICJYSVNUIikKCiMgbWFrZSBzdXJlIElEcyBtYXRjaApzZXhfZ2VuZXNbIXNleF9nZW5lcyAlaW4lIHJvd25hbWVzKGdhbGVuX2FtbF9kb25vcnMpXQoKbGVuZ3RoKHNleF9nZW5lcykKYGBgCgpBbHNvIGV4Y2x1ZGUgZ2VuZXMgZXhwcmVzc2VkIG9uIFkgY2hyb21vc29tZS4KMTQ5IGdlbmVzIHByZXNlbnQgaW4gc2NSTkEtc2VxIGRhdGEuCgpgYGB7cn0KeV9jaHJvbV9nZW5lcyA8LSByZWFkX2xpbmVzKCJyZWZlcmVuY2Vfc291cmNlcy9nZW5jb2RlLnY0NF9nZW5lX25hbWVzX2NoclkudHh0IikKeV9jaHJvbV9nZW5lcyA8LSB5X2Nocm9tX2dlbmVzW3lfY2hyb21fZ2VuZXMgJWluJSByb3duYW1lcyhnYWxlbl9hbWxfZG9ub3JzKV0KbGVuZ3RoKHlfY2hyb21fZ2VuZXMpCgpzZXhfZ2VuZXMgPC0gdW5pcXVlKGMoc2V4X2dlbmVzLCB5X2Nocm9tX2dlbmVzKSkKYGBgCgo2IG91dCBvZiAyMDAwIGhpZ2hseSB2YXJpYWJsZSBnZW5lcyAoQU1MK2hlYWx0aHkpIGhhdmUgc2V4LXNwZWNpZmljIGV4cHJlc3Npb24uCgpJIGRvbid0IHRoaW5rIHJlbW92YWwgb2YgYWxsIGdlbmVzIHRoYXQgYXJlIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBiZXR3ZW4gTSBhbmQgRiBzaG91bGQgYmUgcmVtb3ZlLCBiZWNhdXNlIHRoZXJlIHZhcmlhYmxlcyB0aGF0IGNvLXZhcnkgd2l0aCBzZXguIAoKYGBge3J9CiMgc2V4LXNwZWNpZmljIGdlbmVzIHVzZWQgZm9yIGVtYmVkZGluZwpzZXhfZ2VuZXNbc2V4X2dlbmVzICVpbiUgVmFyaWFibGVGZWF0dXJlcyhnYWxlbl9hbWxfZG9ub3JzKV0KYGBgCgpSZS1jYWxjdWxhdGUgUENBLCBleGNsdWRpbmcgc2V4LXNwZWNpZmljIGdlbmVzIGZyb20gSFZHIGxpc3QuCgpgYGB7ciByZXN1bHRzPUZ9CmdhbGVuX2FtbF9kb25vcnMgPC0KICBSdW5QQ0EoCiAgICBnYWxlbl9hbWxfZG9ub3JzLAogICAgZmVhdHVyZXMgPSBWYXJpYWJsZUZlYXR1cmVzKGdhbGVuX2FtbF9kb25vcnMpWyFWYXJpYWJsZUZlYXR1cmVzKGdhbGVuX2FtbF9kb25vcnMpICVpbiUgc2V4X2dlbmVzXSwKICAgIHJlZHVjdGlvbi5uYW1lID0gInBjYV9ub19zZXgiCiAgKQpgYGAKCgpgYGB7cn0KRWxib3dQbG90KGdhbGVuX2FtbF9kb25vcnMsIG5kaW1zID0gNTAsIHJlZHVjdGlvbiA9ICJwY2Ffbm9fc2V4IikgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLCBOQSkpCmBgYAoKYGBge3IgcmVzdWx0cz1GfQpnYWxlbl9hbWxfZG9ub3JzIDwtCiAgUnVuVU1BUCgKICAgIGdhbGVuX2FtbF9kb25vcnMsCiAgICBkaW1zID0gMToxNSwKICAgIHJlZHVjdGlvbiA9ICJwY2Ffbm9fc2V4IiwKICAgIHJlZHVjdGlvbi5uYW1lID0gInVtYXBfbm9fc2V4IgogICkKYGBgCgpJIHdvdWxkIGNoZWNrIGlmIHRoZSBpbnRlZ3JhdGlvbiBvZiBzZXhlcyBoYXMgaW1wcm92ZWQgaWYgQ2FsY0FsaWdubWVudE1ldHJpYyB3YXMgc3RpbGwgYXZhaWxhYmxlIGluIFNldXJhdCB2NS4uLgoKTWl4aW5nTWV0cmljIGhhcyByZXBsYWNlZCBDYWxjQWxpZ25tZW50TWV0cmljIChTZXVyYXQgdjIpLgoKCkV4cGxhbmF0aW9uIG9mIFtNaXhpbmdNZXRyaWNdKGh0dHBzOi8vZ2l0aHViLmNvbS9zYXRpamFsYWIvSW50ZWdyYXRpb24yMDE5L2lzc3Vlcy8xKQoKPiAxLiBMb29rIGF0IHRoZSBrLm1heCBuZWFyZXN0IG5laWdoYm9ycyBmb3IgYSBjZWxsIGFjcm9zcyBhbGwgZGF0YXNldHMuCiAgMi4gQ29tcHV0ZSB0aGUgayBjbG9zZXN0IG5laWdoYm9ycyBmb3IgZWFjaCBkYXRhc2V0IGluZGl2aWR1YWxseSBmb3IgdGhlIHNhbWUgY2VsbC4KICAzLiBGaW5kIHRoZSByYW5rIGluIHRoZSBvdmVyYWxsIG5laWdoYm9yaG9vZCBsaXN0IChmcm9tIDEuKSB0aGF0IGNvcnJlc3BvbmRzIHRvIHRoZSBrdGggbmVpZ2hib3IgaW4gZWFjaCBkYXRhc2V0IChtYXggb2Ygay5tYXgpIGFuZCB0YWtlIHRoZSBtZWRpYW4gYWNyb3NzIGFsbCBkYXRhc2V0cy4KICA0LiBDb21wdXRlIDEtMyBmb3IgZXZlcnkgY2VsbCBhbmQgYXZlcmFnZS4KICBUaGlzIGF2ZXJhZ2UgaXMgdGhlIHZhbHVlIHRoYXQgaXMgcmV0dXJuZWQgYnkgdGhlIE1peGluZ01ldHJpYyBmdW5jdGlvbi4gSG93ZXZlciwgaXQncyB1c3VhbGx5IG1vcmUgaW50dWl0aXZlIHRvIHRoaW5rIG9mIGhpZ2ggdmFsdWVzIG9mICJtaXhpbmciIHRvIGJlIGJldHRlciBzbyBmb3IgdmlzdWFsaXphdGlvbiwgd2Ugb2Z0ZW4gcGxvdCBtYXguayAtIE1peGluZ01ldHJpYy4KCkRlYWZ1bHQgYG1heC5rID0gMzAwYC4KCkFjY29yZGluZyB0byB0aGlzIG1ldHJpYywgbWl4aW5nIG9mIGNlbGxzIGZyb20gbWFsZSBhbmQgZmVtYWxlIEFNTCBkb25vcnMgaGFzICpzbGlnaHRseSogaW1wcm92ZWQuIAoKYGBge3IgcmVzdWx0cyA9IEZ9Cm1peGluZ19hbWxfc2V4ZXMgPC0gTWl4aW5nTWV0cmljKAogIHN1YnNldChnYWxlbl9hbWxfZG9ub3JzLCBTYW1wbGVDbGFzcyA9PSAiQW1sRG9ub3IiKSwKICByZWR1Y3Rpb24gPSAicGNhIiwKICBkaW1zID0gMToxNSwKICBncm91cGluZy52YXIgPSAiR2VuZGVyIgopCgptaXhpbmdfYW1sX3NleGVzX2NvcnJlY3RlZCA8LSBNaXhpbmdNZXRyaWMoCiAgc3Vic2V0KGdhbGVuX2FtbF9kb25vcnMsIFNhbXBsZUNsYXNzID09ICJBbWxEb25vciIpLAogIHJlZHVjdGlvbiA9ICJwY2Ffbm9fc2V4IiwKICBkaW1zID0gMToxNSwKICBncm91cGluZy52YXIgPSAiR2VuZGVyIgopCmBgYAoKCmBgYHtyfQozMDAgLSBtZWFuKG1peGluZ19hbWxfc2V4ZXMpCjMwMCAtIG1lYW4obWl4aW5nX2FtbF9zZXhlc19jb3JyZWN0ZWQpCmBgYAoKCgpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD03fQojIHNleCBkaWZmZXJlbmNlcwpEaW1QbG90KAogIHN1YnNldChnYWxlbl9hbWxfZG9ub3JzLCBTYW1wbGVDbGFzcyA9PSAiQW1sRG9ub3IiKSwKICByZWR1Y3Rpb24gPSAidW1hcF9ub19zZXgiLAogIGdyb3VwLmJ5ID0gIkdlbmRlciIsCiAgc2h1ZmZsZSA9IFQsCiAgcHQuc2l6ZSA9IDAuMDA1CikgKyBjb29yZF9lcXVhbCgpCmBgYAoKIyMgVGFzayAyOiBNTCBjbGFzc2lmaWVyIG1hbGlnbmFudCBjZWxscwoKKipUYXNrIERlc2NyaXB0aW9uKioKCkFuIGludGVyZXN0aW5nIGZlYXR1cmUgb2YgdGhlIGRhdGFzZXQgaXMgdGhlIGF2YWlsYWJpbGl0eSBvZiBsYWJlbHMgZGlzdGluZ3Vpc2hpbmcgbWFsaWduYW50IGFuZCBub24tbWFsaWduYW50IHNpbmdsZSBjZWxscy4gClRoZSBzZWNvbmQgdGFzayBpcyB0byBleHBsb2l0IHRoZXNlIGxhYmVscyB0byBidWlsZCBhIHN1aXRhYmxlIE1MIGNsYXNzaWZpZXIgdGhhdCBwcmVkaWN0cyB0aGUgbm9ybWFsIG9yIG1hbGlnbmFudCBpZGVudGl0eSBvZiBzaW5nbGUgY2VsbHMuIApQbGVhc2UgZGV0YWlsIG9uIGhvdyB5b3VyIGNsYXNzaWZpZXIgd29ya3MsIHdoZXRoZXIgaXQgaXMgaW50ZXJwcmV0YWJsZSwgaXRzIHBlcmZvcm1hbmNlLCBhbmQgdGhlIHRyYWluLXRlc3Qgc3BsaXQgeW91IHVzZSBmb3IgeW91ciBtYWNoaW5lIGxlYXJuaW5nIGV4cGVyaW1lbnQuICAKCiMjIyBDb25zaWRlcmF0aW9ucyBmb3IgdGhlIHRhc2sKClRoZSB0cmlja3kgcGFydCBpcyBub3QgdHJhaW5pbmcgYSBjbGFzc2lmaWVyLCBidXQgdG8gY29uc2lkZXIgYmlvbG9neSBhbmQgdGVjaG5vbG9neSB0byB0cmFpbiBhICpnb29kKiBjbGFzc2lmaWVyLiAKCkkgYXNzdW1lIHRoYXQgImxhYmVscyBkaXN0aW5ndWlzaGluZyBtYWxpZ25hbnQgYW5kIG5vbi1tYWxpZ25hbnQgc2luZ2xlIGNlbGxzIiBpbiB0aGUgdGFzayBkZXNjcmlwdGlvbiByZWZlcnMgdG8gdGhlIGdlbm90eXBlZCBjZWxscywgYW5kIG5vdCB0aGUgbGFiZWxzIHByZWRpY3RlZCBieSB2YW4gR2FsZW4gZXQgYWwuIGJhc2VkIG9uIHRoZWlyIHJhbmRvbSBmb3Jlc3QgY2xhc3NpZmllcnMuICAKClZhbiBHYWxlbiBldCBhbC4gdHJhaW4gdHdvIHJhbmRvbSBmb3Jlc3QgY2xhc3NpZmllcnMgKHNlZSBiZWxvdyksIGluIG9yZGVyIHRvIGNsYXNzaWZ5IGNlbGxzIGZyb20gQU1MIGRvbm9ycyBpbnRvIG1hbGlnbmFudCBhbmQgbm9ybWFsIGNlbGxzLiAgClRoZSBmaXJzdCBjbGFzc2lmaWVyIGlzIGZvciBIU1BDIGNlbGwgdHlwZXMgYW5kIHRoZSBzZWNvbmQgZm9yIEhTUEMgY2VsbCB0eXBlcyArIG1hbGlnbmFudCB2cy4gbm9ybWFsLiAKRWFjaCBjbGFzc2lmaWVyIGNvbnNpc3RzIG9mIGFuICJvdXRlciIgY2xhc3NpZmllciBmb3IgZmVhdHVyZSBzZWxlY3Rpb24gYW5kIGFuICJpbm5lciIgY2xhc3NpZmllciB0aGF0IGlzIHVzZWQgZm9yIHByZWRpY3Rpb24uIApEdWUgdG8gdmVyeSBsaW1pdGVkIG51bWJlciBvZiBnZW5vdHlwZWQgY2VsbHMgdGhhdCBjYW4gYmUgdXNlZCBmb3IgdHJhaW5pbmcsIHRoZXkgZG8gbm90IGhvbGQgb3V0IGEgdGVzdCBzZXQuIApJbnN0ZWFkLCB0byBldmFsdWF0ZSB0aGVpciBtb2RlbCBwZXJmb3JtYW5jZSwgdGhleSBwZXJmb3JtIDUtZm9sZCBjcm9zcyB2YWxpZGF0aW9uIG9mIHRoZSAiaW5uZXIiIGNsYXNzaWZpZXIuICAKRmluYWxseSwgdGhleSB1c2UgdGhlIG1vZGVsIHRyYWluZWQgb24gYWxsIGF2YWlsYWJsZSBkYXRhIGZvciBjbGFzc2lmaWNhdGlvbiBvZiBBTUwgY2VsbHMgdGhhdCB3ZXJlIG5vdCBnZW5vdHlwZWQuICAKCkkgYXNzdW1lIHRoYXQgYXQgdGhlIHRpbWUgdGhlcmUgd2FzIG5vIGluZGVwZW5kZW50IGRhdGFzZXQgYXZhaWxhYmxlIHdpdGggZ2Vub3R5cGVkIEFNTCBjZWxscyB0byB0ZXN0IGZvciBnZW5lcmFsaXphYmlsaXR5IG9mIHRoZSBtb2RlbCB0byBvdGhlciBzY1JOQS1zZXEgdGVjaG5vbG9naWVzIChkcm9wbGV0LWJhc2VkLCA1JyBzZXF1ZW5jaW5nKSwgaHVtYW4gcG9wdWxhdGlvbnMsIG9yIGRyaXZlciBtdXRhdGlvbnMuIAoKKipQb2ludHMgb2YgY3JpdGlxdWUqKgoKMS4gSW5jbHVkaW5nIGFsbCB0cmFpbmluZyBkYXRhIGluIHRoZSBmZWF0dXJlIHNlbGVjdGlvbiBtYWtlcyB0aGUgQ1YgcmVzdWx0cyBvZiB0aGUgImlubmVyIiBjbGFzc2lmaWVyIGxlc3MgaW5mb3JtYXRpdmUuCjIuIFRoZSBDViBpcyBzdXBwb3NlZCB0byBldmFsdWF0ZSBvdmVyLSBhbmQgdW5kZXJmaXR0aW5nLgogICAgVG8gZXN0aW1hdGUgb3ZlcmZpdHRpbmcgKGNvbXBsZXhpdHkgb2YgdGhlIG1vZGVsKSwgdGhlIG1vZGVsIHNob3VsZCBiZSBldmFsdWF0ZWQgb24gaG93IHdlbGwgaXQgZXh0cmFwb2xhdGVzIHRvIGFub3RoZXIgZG9ub3IgYW5kIHRvIGFub3RoZXIgZHJpdmVyIG11dGF0aW9uLCBub3QgYSByYW5kb20gc2FtcGxlIG9mIGFsbCB0cmFpbmluZyBjZWxscy4KMy4gQWxsIGhlYWx0aHkgY2VsbHMgaW4gdGhlIHRyYWluaW5nIGRhdGEgYXJlIG1hbGUuIFZhbiBHYWxlbiBkbyBub3QgYWRkcmVzcyB0aGlzIGJ5IHJlbW92aW5nIHNleC1zcGVjaWZpYyBnZW5lcyBmcm9tIHRoZSBmZWF0dXJlcy4gCiAgICAoSSdtIGhvbmVzdGx5IHN1cHJpc2VkIHRoYXQgdGhlIGNsYXNzaWZpZXIgZG9lc24ndCBwZXJmb3JtIGV2ZW4gd29yc2Ugb24gZmVtYWxlIHNhbXBsZXMgYmFzZWQgb24gYmxhc3QgY291bnQgdGhhbiBpdCBkb2VzLiAKICAgIEEgbWFsZSBzYW1wbGUgd2l0aCBsb3NzIG9mIHkgbWlnaHQgcGxheSBhIHJvbGUsIGFuZCBleHByZXNzaW9uIGxldmVsL3NwYXJzaXR5IG9mIHNleC1zcGVjaWZpYyBnZW5lcy4pCjQuIENlbGxzIHdlcmUgZ2Vub3R5cGVkIGJhc2VkIG9uIHRyYW5zY3JpcHRzIG9mIGZvbGxvd2luZyBnZW5lczogIAogICAgVEVUMiwgU0VURDIsIFBUUE4xMSwgTlJBUywgU0YzQTEsIEJDT1IsIEtJVCwgUkFEMjEsIFRQNTMsIEJSQ0MzLCBSVU5YMSwgRkxUMywgSURIMiwgRE5NVDNBLCBTTUMzLCBOUE0xLCBLUkFTLiAgCiAgICBDZWxscyB3aXRoIGhpZ2hlciBleHByZXNzaW9uIG9mIHRoZXNlIGdlbmVzIGFyZSBtb3JlIGxpa2VseSB0byBiZSBnZW5vdHlwZWQgYW5kIG1ha2UgaXQgaW50byB0aGUgdHJhaW5pbmcgZGF0YS4KICAgIFRoZWlyIGV4cHJlc3Npb24gc2hvdWxkIGJlIGV4Y2x1ZGVkIGZyb20gdGhlIGZlYXR1cmUgbGlzdCwgYWxzbyB0byBtYWtlIGl0IG1vcmUgZ2VuZXJhbGlzYWJsZSB0byBvdGhlciBkcml2ZXIgbXV0YXRpb25zLiAKNS4gVGhlIGNsYXNzaWZpZXIgZG9lcyBub3QgY29uc2lkZXIgcG9zc2libGUgZGlmZmVyZW5jZXMgaW4gdHJhbnNjcmlwdGlvbiBiZXR3ZWVuIG5vbi1tYWxpZ25hbnQgSFNQQ3MgZnJvbSBBTUwgZG9ub3JzIGFuZCBoZWFsdGh5IGRvbm9ycy4gCiAgICBOb24tbWFsaWduYW50IGNlbGxzIGZyb20gQU1MIGRvbm9ycyBoYXZlIHBvc3NpYmx5IGFsdGVyZWQgdHJhbnNjcmlwdGlvbiBkdWUgdG8gY2hhbmdlcyBpbiB0aGUgZW52aXJvbm1lbnQgaW4gdGhlIGJvbmUgbWFycm93LiAgCiAgICBUaGUgY2xhc3NpZmllciBvbmx5IGNvbnNpZGVycyBoZWFsdGh5IGNlbGxzIGZyb20gaGVhbHRoeSBkb25vcnMsIGFuZCBub3Qgbm9uLW1hbGlnbmFudCBjZWxscyBmcm9tIEFNTCBkb25vcnMuIAogICAgVGhpcyBpcyBhIGxpbWl0YXRpb24gb2YgdGhlIHRlY2hub2xvZ3ksIGJlY2F1c2UgY2VsbHMgd2l0aCB3dCB0cmFuc2NyaXB0IGRldGVjdGVkIGJ1dCBubyBtdXRhdGVkIHRyYW5zY3JpcHQgZGV0ZWN0ZWQgY2Fubm90IGNvbmZpZGVudGx5IGJlIGNsYXNzaWZpZWQgYXMgbm9ybWFsLCBkdWUgdG8gY2xvbmFsaXR5IGFuZCBoZXRlcm96eWdvc2l0eS4gIAogICAgSG93ZXZlciwgdGhlcmUgbWlnaHQgYmUgYSBzdWJzZXQgb2YgY2VsbHMgZnJvbSBkb25vcnMgd2l0aCBrbm93biB6eWdvc2l0eSBhbmQgY2xvbmFsIHN0cnVjdHVyZSB3aGVyZSB0aGlzIGlzIHBvc3NpYmxlLiAKCldoYXQgSSBhZ3JlZSB3aXRoLCBpcyB0aGUgb2JzZXJ2YXRpb24gdGhhdCwgZ2VuZXJhbGx5LCBtYWxpZ25hbnQgSFNDcyBhcmUgdHJhbnNjcmlwdGlvbmFsbHkgbW9yZSBzaW1pbGFyIHRvIGhlYWx0aHkgSFNDcyB0aGFuIHRvIG1hbGlnbmFudCBtb25vY3l0ZSBwcm9nZW5pdG9ycy4KVGhlcmVmb3JlLCBpdCBtYWtlcyBzZW5zZSB0byBndWlkZSB0aGUgbW9kZWwgd2l0aCBpbmZvcm1hdGlvbiBvbiBzaW1pbGFyaXR5IHRvd2FyZHMgaGVhbHRoeSBIU1BDIGNlbGwgdHlwZXMuICAKClJhbmRvbSBmb3Jlc3QgY2xhc3NpZmllcnMgYWxsb3cgdGhlIGV4dHJhY3Rpb24gb2YgZmVhdHVyZSBpbXBvcnRhbmNlIG1ldHJpY3MgKGUuZy4gbnVtYmVyIG9mIHRyZWVzIGluIHdoaWNoIGEgZmVhdHVyZSBpcyB1c2VkKS4KSG93ZXZlciwgb25lIGhhcyB0byBkaWZmZXIgYmV0d2VlbiBhICptaW5pbWFsIG9wdGltYWwgc3Vic2V0KiBhbmQgYW4gKmFsbCByZWxldmFudCBzdWJzZXQqIG9mIGdlbmVzLiAKVGhpcyBpcyBlc3BlY2lhbGx5IHJlbGV2YW50IGZvciBnZW5lLWV4cHJlc3Npb24gYmFzZWQgY2xhc3NpZmllcnMgZHVlIHRvIHJlZHVuZGFuY2UgKHRoaW5rIGdlbmUgbW9kdWxlcy9zaWduYXR1cmVzKS4KU2VlIFtLdXJzYSBldCBhbC4gMjAxNF0oaHR0cHM6Ly9ibWNiaW9pbmZvcm1hdGljcy5iaW9tZWRjZW50cmFsLmNvbS9hcnRpY2xlcy8xMC4xMTg2LzE0NzEtMjEwNS0xNS04KSBmb3IgbW9yZSBkZXRhaWxzLiAKClZhbiBHYWxlbiBldCBhbC4gc2VsZWN0IGFzIGZlYXR1cmVzIHRoZSBnZW5lcyBjaG9zZW4gbW9zdCBvZnRlbiBpbiB0aGUgb3V0ZXIgY2xhc3NpZmllciwgYWNyb3NzIDEwMDAgdHJlZXMuIFRoaXMgZG9lcyBub3Qgc2VlbSB0byBjb25zaWRlciBjbGFzc2VzLiAKCioqSWRlYXMqKgoKLSBJbnN0ZWFkIG9mIHByZWRpY3RpbmcgSFNDIGFuZCBIU0MtbGlrZSBjZWxscywgd2h5IG5vdCBnaXZlIGEgc2ltaWxhcml0eSBzY29yZSB0b3dhcmRzIGVhY2ggaGVhbHRoeSBjZWxsIHR5cGUgYXMgaW5wdXQgZmVhdHVyZS4gCiAgICBUaGlzIHdvdWxkIGFsbG93IHRvIGluZm9ybSB0aGUgY2xhc3NpZmllciBmb3IgcmVsZXZhbnQgZmVhdHVyZXMgbGlrZSBjb21iaW5hdGlvbnMgb2Ygc2lnbmF0dXJlcyAoZS5nLiBMU0NzIGFyaXNpbmcgZnJvbSBHTVBzIHdpdGggbWl4ZWQgSFNDL0dNUCBzaWduYXR1cmUpLgotIFdoeSBpbmNsdWRlIGx5bXBob2lkIGFuZCBlcnl0aHJvaWQgY2VsbCB0eXBlcyBpbiB0aGUgbWFsaWduYW50IHZzLiBub24tbWFsaWduYW50IGNsYXNzaWZpZXI/IAogICAgVGhleSBhcmUgdHJhbnNjcmlwdGlvbmFsbHkgdmVyeSBkaXN0aW5jdCBhbmQgaW4gQU1MIHRoZXkgYXJlIG5vdCBtYWxpZ25hbnQuIAogICAgVGhlIGNsYXNzaWZpZXIgbWlnaHQgaGF2ZSBhbiBlYXNpZXIgdGltZSBpZiB0aGV5IGFyZSBleGNsdWRlZC4gCi0gQ29uc2lkZXIgb3RoZXIgbWV0aG9kcyB0byBhc3NpZ24gdGhlIG1vc3Qgc2ltaWxhciBoZWFsdGh5IGNlbGwgdHlwZSwgc3VjaCBhcyBsYWJlbCB0cmFuc2ZlciB2aWEgcHJvamVjdGlvbiBvbnRvIHRoZSBoZWFsdGh5IHJlZmVyZW5jZSAoQXppbXV0aCkuIAotIFVzZSBhbiBpbmRlcGVuZGVudCB0ZXN0IHNldCBvZiBnZW5vdHlwZXMgQU1MIGNlbGxzLiAKCioqVmFuIEdhbGVuIGNsYXNzaWZpZXIgbWFsaWduYW50IHZzLiBub3JtYWwgQU1MIGNlbGxzKioKCmBgYHtyIGVjaG8gPSBGLCBvdXQud2lkdGg9IjEwMCUiLCBmaWcud2lkdGg9MTV9Cm5vbW5vbWw6Om5vbW5vbWwoY29kZSA9ICIjZGlyZWN0aW9uOiBkb3duCls8ZnJhbWU+SFNQQyBjZWxsdHlwZSBhbm5vdGF0aW9uIHwKW0hlYWx0aHkgY2VsbHNdIC0gCls8c3RhdGU+IEJhY2tzcGluIGNsdXN0ZXJpbmcgfCBNYW51YWwgSFNQQyBjZWxsIHR5cGUgYW5ub3RhdGlvbl0gLSAKW0hlYWx0aHkgY2VsbHMsIGFubm90YXRlZCBieSBIU1BDIGNlbGwgdHlwZXNdIC0gWzxzdGF0ZT4gVHJhaW4gUkZdIC0gCltDbGFzc2lmaWVyIGZvciBIU1BDIGNlbGwgdHlwZXNdIC0gWzxzdGF0ZT4gUHJlZGljdF0KW0FNTCBjZWxsc10gLSBbPHN0YXRlPiBQcmVkaWN0XSAtCltBTUwgY2VsbHMsIGFubm90YXRlZCBieSBIU1BDIGNlbGwgdHlwZXNdCl0KCls8ZnJhbWU+TWFsaWduYW50IGNlbGx0eXBlIGFubm90YXRpb24gfApbQU1MIGNlbGxzLCBnZW5vdHlwZWQgbWFsaWduYW50IHwgSFNQQy1saWtlIGNlbGwgdHlwZSBsYWJlbHNdIC0gWzxzdGF0ZT4gVHJhaW4gUkZdCltIZWFsdGh5IGNlbGxzIHwgSFNQQyBjZWxsIHR5cGUgbGFiZWxzXSAtIFs8c3RhdGU+IFRyYWluIFJGXSAtCltDbGFzc2lmaWVyIGZvciBIU1BDICYgSFNQQy1saWtlIGNlbGwgdHlwZXNdIC0gWzxzdGF0ZT4gUHJlZGljdF0KW0FNTCBjZWxscywgbm90IGdlbm90eXBlZCBtYWxpZ25hbnQgfCBIU1BDLWxpa2UgY2VsbCB0eXBlIGxhYmVsc10gLSBbPHN0YXRlPiBQcmVkaWN0XSAtCltBTUwgY2VsbHMsIGFubm90YXRlZCBieSBIU1BDICYgSFNQQy1saWtlIGNlbGwgdHlwZXNdCl0iKQpgYGAKCgojIyMgVHJhbnNjcmlwdGlvbmFsIGhldGVyb2dlbmVpdHkgb2YgbWFsaWduYW50IGFuZCBoZWFsdGh5IGNlbGxzCgpUaGUgZ29hbCBpcyB0byBkZXZlbG9wIGEgY2xhc3NpZmllciBvZiBtYWxpZ25hbnQgdnMuIGhlYWx0aHkgSFNQQ3MgYW5kIG15ZWxvaWQgY2VsbHMgZnJvbSBBTUwgcGF0aWVudHMsIHRoYXQgaXMgZ2VuZXJhbGl6YWJsZSB0byBvdGhlciBBTUwgdHlwZXMgd2l0aCBvdGhlciBkcml2ZXIgbXV0YXRpb25zIGFuZCBvdGhlciBzY1JOQS1zZXEgdGVjaG5vbG9naWVzL2RhdGFzZXRzLiAKCkV2YWx1YXRlIHRoZSB0cmFuc2NyaXB0aW9uYWwgaGV0ZXJvZ2VuZWl0eSBvZiBBTUwgc3VidHlwZXMgYW5kIG1hbGlnbmFudCB2cy4gaGVhbHRoeSBjZWxscy4KClExOiBEbyBoZWFsdGh5IGNlbGxzIGRpZmZlciBmcm9tIG1hbGlnbmFudCBjZWxscyBiYXNlZCBvbiB0aGVpciB0cmFuc2NyaXB0b21lPwoKUTI6IElzIHRoZSBtYWxpZ25hbnQgdHJhbnNjcmlwdGlvbmFsIHByb2ZpbGUgc3BlY2lmaWMgdG8gZHJpdmVyIG11dGF0aW9ucz8KCldlIGFyZSBvbmx5IGludGVyZXN0ZWQgaW4gbXllbG9pZCBhbmQgSFNQQyBjZWxscyBoZXJlLCBiZWNhdXNlIGRpc3RpbmN0aW9uIG9mIGVyeXRocm9pZCBhbmQgbHltcGhvaWQgY2VsbHMgaXMgcmVsYXRpdmVseSBlYXN5LiAKVGhvc2UgY2VsbHMgYXJlIG5vdCBtYWxpZ25hbnQgaW4gYWN1dGUgKm15ZWxvaWQqIGxldWtlbWlhLiAKCkZvciBhIHN0YXJ0LCB1c2UgSFNQQ3MgKyBteWVsb2lkIGNlbGxzLCBhY2NvcmRpbmcgdG8gdmFuIEdhbGVuIGNlbGwgdHlwZSBhbm5vdGF0aW9uLiAgCgo8IS0tIE9ubHkgY29uc2lkZXIgc2FtcGxlcyB3aXRoIGJsYXN0IGNvdW50ID4gMC4xIGFuZCBhdCB0aW1lIG9mIGRpYWdub3Npcy4gIC0tPgo8IS0tIFRoZXNlIHdlcmUgMiB2YXJpYWJsZXMgdGhhdCBhY2NvdW50ZWQgZm9yIG1vc3Qgb2YgdGhlIHZhcmlhbmNlIGluIHRoZSBkYXRhLiAgLS0+CgpJbmNsdWRlIGhlYWx0aHkgZG9ub3JzLCB0byBzZWUgaWYgdGhlIGNlbGxzIGdlbm90eXBlZCBhcyB3dCBncm91cCB3aXRoIGRlZmluaXRpdmUgaGVhbHRoeSBjZWxscy4gCgpgYGB7cn0Kc2VsZWN0ZWRfc2FtcGxlcyA8LSBkb25vcl9tZXRhZGF0YSAlPiUKICAjIGZpbHRlcigKICAjICAgIWdyZXBsKCJCTSIsIFNhbXBsZSksCiAgIyAgICMgYEJsYXN0IGNvdW50YCA+IDAuMSwKICAjICAgIyBgRGF5cyBmcm9tIGRpYWdub3Npc2AgPT0gIkQwIgogICMgICApICU+JQogIHB1bGwoU2FtcGxlSWQpCgpnYWxlbl9hbWxfZG9ub3JzX215ZWxvaWRfaHNwYyA8LQogIHN1YnNldCgKICAgIGdhbGVuX2FtbF9kb25vcnMsCiAgICBzdWJzZXQgPSAob3JpZy5pZGVudCAlaW4lIHNlbGVjdGVkX3NhbXBsZXMgJiBTYW1wbGVDbGFzcyA9PSAiSGVhbHRoeURvbm9yIiB8IAogICAgICAoKENlbGxUeXBlR2VuZXJhbCA9PSAiTXllbG9pZC9IU1BDLWxpa2UiIHwKICAgICAgQ2VsbFR5cGVHZW5lcmFsID09ICJNeWVsb2lkL0hTUEMiKSAmIGdlbm90eXBlICE9ICJub3RfZGV0ZWN0ZWQiKSkKICApCnRhYmxlKGdhbGVuX2FtbF9kb25vcnNfbXllbG9pZF9oc3BjJGdlbm90eXBlKQoKIyBvbmx5IGtlZXAgU2FtcGxlSWQtZ2Vub3R5cGUgY29tYmluYXRpb25zIHdpdGggYXQgbGVhc3QgMTAgY2VsbHMKc2FtcGxlc19lbm91Z2hfY2VsbHMgPC0gbmFtZXMod2hpY2godGFibGUoZ2FsZW5fYW1sX2Rvbm9yc19teWVsb2lkX2hzcGMkb3JpZy5pZGVudCkgPj0gMTApKQpnYWxlbl9hbWxfZG9ub3JzX215ZWxvaWRfaHNwYyA8LQogIHN1YnNldCgKICAgIGdhbGVuX2FtbF9kb25vcnNfbXllbG9pZF9oc3BjLAogICAgc3Vic2V0ID0gb3JpZy5pZGVudCAlaW4lIHNhbXBsZXNfZW5vdWdoX2NlbGxzCiAgKQpgYGAKCmBgYHtyfQpnYWxlbl9hbWxfZG9ub3JzX215ZWxvaWRfaHNwY0BtZXRhLmRhdGEgPC0gZ2FsZW5fYW1sX2Rvbm9yc19teWVsb2lkX2hzcGNAbWV0YS5kYXRhICU+JQogIG11dGF0ZSgKICAgIFNhbXBsZUlkX2dlbm90eXBlID0KICAgICAgcGFzdGUoCiAgICAgICAgZ2FsZW5fYW1sX2Rvbm9yc19teWVsb2lkX2hzcGMkb3JpZy5pZGVudCwKICAgICAgICBnYWxlbl9hbWxfZG9ub3JzX215ZWxvaWRfaHNwYyRnZW5vdHlwZSwKICAgICAgICBzZXAgPSAiXyIKICAgICAgKQogICkgJT4lCiAgbXV0YXRlKFNhbXBsZUlkX2dlbm90eXBlID0gaWZlbHNlKFNhbXBsZUNsYXNzID09ICJIZWFsdGh5RG9ub3IiLCBnc3ViKCJub3RfZGV0ZWN0ZWQiLCAiaGVhbHRoeSIsIFNhbXBsZUlkX2dlbm90eXBlKSwgU2FtcGxlSWRfZ2Vub3R5cGUpKQpgYGAKCgpgYGB7cn0KcGJfY291bnRzIDwtCiAgU2V1cmF0OjpBZ2dyZWdhdGVFeHByZXNzaW9uKAogICAgZ2FsZW5fYW1sX2Rvbm9yc19teWVsb2lkX2hzcGMsCiAgICBhc3NheXMgPSAiUk5BIiwKICAgIHNsb3QgPSAiY291bnRzIiwKICAgICMgZ3JvdXAuYnkgPSAiU2FtcGxlSWRfQ2VsbFR5cGVHZW5lcmFsIgogICAgZ3JvdXAuYnkgPSAiU2FtcGxlSWRfZ2Vub3R5cGUiCiAgICAjIGdyb3VwLmJ5ID0gIm9yaWcuaWRlbnQiCiAgKSRSTkEKCmdyb3VwX2Fubm8gPC0gY29sbmFtZXMocGJfY291bnRzKQpncm91cF9hbm5vIDwtIGdzdWIoIi4qLSIsICIiLCBncm91cF9hbm5vKQogCnBiX2RnZSA8LSBlZGdlUjo6REdFTGlzdCgKICAgIGNvdW50cyA9IHBiX2NvdW50cywKICAgIHNhbXBsZXMgPSBjb2xuYW1lcyhwYl9jb3VudHMpLAogICAgZ3JvdXAgPSBncm91cF9hbm5vCikKYGBgCgpGaWx0ZXIgb3V0IHNhbXBsZXMgd2l0aCBsb3cgcmVhZCBjb3VudCAoPWZldyBmaWx0ZXJlZCBjZWxscykuICAKS2VlcCBzYW1wbGVzIHdpdGggcmVhZCBjb3VudHMgPiA1MCwwMDAsIHdoaWNoIGlzIGFsbCBzYW1wbGVzLgoKYGBge3J9CnN1bW1hcnkocGJfZGdlJHNhbXBsZXMkbGliLnNpemUpCgprZWVwLnNhbXBsZXMgPC0gcGJfZGdlJHNhbXBsZXMkbGliLnNpemUgPiA1ZTQKdGFibGUoa2VlcC5zYW1wbGVzKQoKIyBwYl9kZ2UgPC0gcGJfZGdlWywga2VlcC5zYW1wbGVzXQpgYGAKCkZpbHRlciBvdXQgbG93bHkgZXhwcmVzc2VkIGdlbmVzLiAgCktlZXAgZ2VuZXMgd2l0aCBhdCBsZWFzdCAxMCBDUE0gaW4gYXQgbGVhc3QgMiBzYW1wbGVzLgogCmBgYHtyfQojIGdlbmVzIHdpdGggYXQgbGVhc3QgMSBDUE0gaW4gYXQgbGVhc3QgMiBzYW1wbGVzCmtlZXAuZ2VuZXMgPC0gcm93U3VtcyhlZGdlUjo6Y3BtKHBiX2RnZSkgPj0xMCkgPj0gMgp0YWJsZShrZWVwLmdlbmVzKQogCnBiX2RnZSA8LSBwYl9kZ2Vba2VlcC5nZW5lcywgLCBrZWVwPUZBTFNFXQpgYGAKClRNTSBub3JtYWxpemF0aW9uIGlzIHBlcmZvcm1lZCB0byBlc3RpbWF0ZSBlZmZlY3RpdmUgbGlicmFyeSBzaXplLgogCmBgYHtyfQpwYl9kZ2UgPC0gZWRnZVI6OmNhbGNOb3JtRmFjdG9ycyhwYl9kZ2UsIG1ldGhvZCA9ICJUTU0iKQpgYGAKIApDYWxjdWxhdGUgTXVsdGlkaW1lbnNpb25hbCBzY2FsaW5nIChNRFMpIHBsb3Qgb2YgZGlzdGFuY2VzIGJldHdlZW4gZ2VuZSBleHByZXNzaW9uIHByb2ZpbGVzLgogCmBgYHtyfQpwYl9tZHMgPC0gbGltbWE6OnBsb3RNRFMocGJfZGdlLCBwbG90ID0gRikKCnBiX21kc19kZiA8LSBkYXRhLmZyYW1lKAogIGxpc3QoCiAgICB4ID0gcGJfbWRzJHgsCiAgICB5ID0gcGJfbWRzJHksCiAgICBsaWIuc2l6ZSA9IHBiX2RnZSRzYW1wbGVzJGxpYi5zaXplLAogICAgc2FtcGxlID0gZ3N1YigiLS4qIiwgIiIsIGNvbG5hbWVzKHBiX2RnZSkpLAogICAgZ3JvdXAgPSBwYl9kZ2Ukc2FtcGxlcyRncm91cAogICkKKSAlPiUKICBtZXJnZShkb25vcl9tZXRhZGF0YSwgYnkueCA9ICJzYW1wbGUiLCBieS55ID0gIlNhbXBsZUlkIiwgYWxsLnggPSBUKQpgYGAKCkNoZWNrIHRoYXQgbWFqb3Igc291cmNlIG9mIHZhcmlhdGlvbiBpcyBub3QgdGVjaG5pY2FsIChzYW1wbGUgc2l6ZSkgb3IgaXJyZWxldmFudCBiaW9sb2dpY2FsIGluZm9ybWF0aW9uIGluIHRoaXMgY2FzZSAoYmxhc3QgY291bnQpLgoKYGBge3IgZmlnLmhlaWdodD0zLjUsIGZpZy53aWR0aD01fQp4bGFiX3RleHQgPC0gcGFzdGUwKAogICAgIkxlYWRpbmcgbG9nRkMgZGltIDEgKCIsCiAgICByb3VuZChwYl9tZHMkdmFyLmV4cGxhaW5lZFsxXSAqIDEwMCwgMSksCiAgICAiJSkiCiAgKQp5bGFiX3RleHQgPC0gcGFzdGUwKAogICAgIkxlYWRpbmcgbG9nRkMgZGltIDIgKCIsCiAgICByb3VuZChwYl9tZHMkdmFyLmV4cGxhaW5lZFsyXSAqIDEwMCwgMSksCiAgICAiJSkiCiAgKQoKbWRzX3RoZW1lIDwtIHRoZW1lX2J3KCkgKwogIHRoZW1lKHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpCgpwYl9tZHNfZGYgJT4lCiAgdW5pcXVlKCkgJT4lCiAgZ2dwbG90KGFlcyh4LCB5LCBsYWJlbCA9IHNhbXBsZSkpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGxvZyhsaWIuc2l6ZSkpKSArCiAgIyBnZ3JlcGVsOjpnZW9tX3RleHRfcmVwZWwoKSArCiAgeGxhYih4bGFiX3RleHQpICsgeWxhYih5bGFiX3RleHQpICsKICBtZHNfdGhlbWUKCnBiX21kc19kZiAlPiUKICBnZ3Bsb3QoYWVzKHgsIHksIGxhYmVsID0gc2FtcGxlKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gYEJsYXN0IGNvdW50YCkpICsKICAjIGdncmVwZWw6Omdlb21fdGV4dF9yZXBlbCgpICsKICB4bGFiKHhsYWJfdGV4dCkgKyB5bGFiKHlsYWJfdGV4dCkgKwogIG1kc190aGVtZQoKcGJfbWRzX2RmICU+JQogIGdncGxvdChhZXMoeCwgeSwgbGFiZWwgPSBzYW1wbGUpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBgRGF5cyBmcm9tIGRpYWdub3Npc2AgPT0gIkQwIikpICsKICAjIGdncmVwZWw6Omdlb21fdGV4dF9yZXBlbCgpICsKICB4bGFiKHhsYWJfdGV4dCkgKyB5bGFiKHlsYWJfdGV4dCkgKwogIG1kc190aGVtZQoKcGJfbWRzX2RmICU+JQogIGdncGxvdChhZXMoeCwgeSwgbGFiZWwgPSBzYW1wbGUpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBncm91cCkpICsKICAjIGdncmVwZWw6Omdlb21fdGV4dF9yZXBlbCgpICsKICB4bGFiKHhsYWJfdGV4dCkgKyB5bGFiKHlsYWJfdGV4dCkgKwogIG1kc190aGVtZQoKcGJfbWRzX2RmICU+JQogIGdncGxvdChhZXMoeCwgeSwgbGFiZWwgPSBzYW1wbGUpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBHZW5kZXIpKSArCiAgIyBnZ3JlcGVsOjpnZW9tX3RleHRfcmVwZWwoKSArCiAgeGxhYih4bGFiX3RleHQpICsgeWxhYih5bGFiX3RleHQpICsKICBtZHNfdGhlbWUKCnBiX21kc19kZiAlPiUKICBnZ3Bsb3QoYWVzKHgsIHksIGxhYmVsID0gc2FtcGxlKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gU2FtcGxlKSkgKwogIGdncmVwZWw6Omdlb21fdGV4dF9yZXBlbCgKICAgIHNpemUgPSAyLAogICAgbWluLnNlZ21lbnQubGVuZ3RoID0gMCwKICAgIG1heC5vdmVybGFwcyA9IEluZgogICkgKwogIHhsYWIoeGxhYl90ZXh0KSArIHlsYWIoeWxhYl90ZXh0KSArCiAgbWRzX3RoZW1lICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiTm9uZSIpCmBgYAoKCkNoZWNrIGlmIHRoZSBzYW1wbGVzIHNwbGl0IGJ5IG11dGF0aW9uLgoKYGBge3J9CnNhbXBsZUlkX211dGF0aW9uX2xpc3QgPC0gZG9ub3JfbWV0YWRhdGEgJT4lCiAgc2VsZWN0KFNhbXBsZUlkLCBgUkhQIE11dGF0aW9uc2AsIGBDb21tb24gdHJhbnNsb2NhdGlvbmApICU+JQogIGZpbHRlcihTYW1wbGVJZCAlaW4lIHBiX21kc19kZiRzYW1wbGUpICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gYyhgUkhQIE11dGF0aW9uc2AsIGBDb21tb24gdHJhbnNsb2NhdGlvbmApLCB2YWx1ZXNfdG8gPSAiZHJpdmVyX211dGF0aW9uIikgJT4lCiAgc2VsZWN0KC1uYW1lKSAlPiUKICBmaWx0ZXIoIWRyaXZlcl9tdXRhdGlvbiAlaW4lIGMoIk5vdCBwZXJmb3JtZWQiLCAiTm9uZSBEZXRlY3RlZCIsICJVbmtub3duIiwgIk5BIikgJiAhaXMubmEoZHJpdmVyX211dGF0aW9uKSkgJT4lCiAgc2VwYXJhdGVfbG9uZ2VyX2RlbGltKGNvbHMgPSBkcml2ZXJfbXV0YXRpb24sICIvLy8gIikgJT4lCiAgbXV0YXRlKGRyaXZlcl9tdXRhdGlvbiA9IGdzdWIoIiAuKiIsICIiLCBkcml2ZXJfbXV0YXRpb24pKSAlPiUKICAjIHVuaXF1ZSBmb3Igc2FtcGxlcyB3aXRoIG11bHRpcGxlIG11dGF0aW9ucyBpbiBzYW1lIGdlbmUKICB1bmlxdWUoKSAlPiUKICBtdXRhdGUodmFsdWUgPSBUKSAlPiUKICBwaXZvdF93aWRlcihpZF9jb2xzID0gU2FtcGxlSWQsIG5hbWVzX2Zyb20gPSBkcml2ZXJfbXV0YXRpb24sIHZhbHVlc19maWxsID0gRikgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSAtU2FtcGxlSWQsIG5hbWVzX3RvID0gImRyaXZlcl9tdXRhdGlvbiIpCmBgYAoKCmBgYHtyfQpwYl9tZHNfZGZfZHJpdmVyX211dGF0aW9uIDwtIG1lcmdlKHBiX21kc19kZiwgc2FtcGxlSWRfbXV0YXRpb25fbGlzdCwgYnkueCA9ICJzYW1wbGUiLCBieS55ID0gIlNhbXBsZUlkIiwgYWxsLnggPSBUKQpgYGAKClBDMSBhbGlnbnMgd2l0aCBETk1UQTMuIEluZGljYXRpb24gdGhhdCBETk1UMyBoYXMgYSBkaXN0aW5jdCB0cmFuc2NyaXB0aW9uYWwgcHJvZmlsZS4gCgoiRE5NVDNBIG11dGF0aW9ucyBvY2N1ciBpbiBhcHByb3hpbWF0ZWx5IDIwJSBvZiBBTUwgY2FzZXMgYW5kIGFyZSBhc3NvY2lhdGVkIHdpdGggY2hhbmdlcyBpbiBETkEgbWV0aHlsYXRpb24uIENES04yQiBwbGF5cyBhbiBpbXBvcnRhbnQgcm9sZSBpbiB0aGUgcmVndWxhdGlvbiBvZiBoZW1hdG9wb2lldGljIHByb2dlbml0b3IgY2VsbHMgYW5kIEROTVQzQSBtdXRhdGlvbiBpcyBhc3NvY2lhdGVkIHdpdGggQ0RLTjJCIHByb21vdGVyIG1ldGh5bGF0aW9uLiIKaHR0cHM6Ly9wbWMubmNiaS5ubG0ubmloLmdvdi9hcnRpY2xlcy9QTUM3MTA2MTIyLwoKS1JBUywgRkxUMy1JVEQsIE5QTTEsIE5SQVMKCmBgYHtyLCBmaWcuaGVpZ2h0PTh9CnBiX21kc19kZl9kcml2ZXJfbXV0YXRpb24gJT4lCiAgZmlsdGVyKGdyb3VwID09ICJtYWxpZ25hbnQiKSAlPiUKICBnZ3Bsb3QoYWVzKHgsIHksIGxhYmVsID0gc2FtcGxlKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gdmFsdWUpKSArCiAgZmFjZXRfd3JhcCh+ZHJpdmVyX211dGF0aW9uKSArCiAgeGxhYih4bGFiX3RleHQpICsgeWxhYih5bGFiX3RleHQpICsKICB0aGVtZV9idygpICsKICB0aGVtZShwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKSArCiAgY29vcmRfZXF1YWwoKQpgYGAKCmBgYHtyfQp1bmlxdWUoc2FtcGxlSWRfbXV0YXRpb25fbGlzdCRkcml2ZXJfbXV0YXRpb24pCmdlbmVzX2dlbm90eXBlcyA8LQogIGMoCiAgICAiTlJBUyIsCiAgICAiS1JBUyIsCiAgICAiU0YzQTEiLAogICAgIk5PVENIMSIsCiAgICAiTk9UQ0gyIiwKICAgICJTRjNBMSIsCiAgICAiQ0JGQiIsCiAgICAiTVlIMTEiLAogICAgIk5QTTEiLAogICAgIkpBSzMiLAogICAgIkROTVQzQSIsCiAgICAiRkxUMyIsCiAgICAiVFA1MyIsCiAgICAiU0VURDIiLAogICAgIlJVTlgxIiwKICAgICJCQ09SIiwKICAgICJCQ09STDEiLAogICAgIlBUUE4xMSIsCiAgICAiU01DMyIsCiAgICAiSURIMiIsCiAgICAiVEVUMiIsCiAgICAiQlJDQzMiLAogICAgIktJVCIsCiAgICAiUkFEMjEiLAogICAgIk1MTCIsCiAgICAiQ0VCUEEiCiAgKQpgYGAKCiMjIyBBemltdXRoIHByb2plY3Rpb24KCkFsdGVybmF0aXZlIHRvIGNlbGwgdHlwZSBpZGVudGlmaWNhdGlvbiB3aXRoIGEgcmFuZG9tIGZvcmVzdCBtb2RlbCBpcyBsYWJlbCB0cmFuc2ZlciBmcm9tIGEgcmVmZXJlbmNlIGF0bGFzLiAKClBlcmZvcm0gbGFiZWwgdHJhbnNmZXIgZnJvbSB0aGUgQXppbXV0aCBib25lIG1hcnJvdyByZWZlcmVuY2UgYXRsYXMuIApJbnN0ZWFkIG9mIHRyYWluaW5nIGEgcmFuZG9tIGZvcmVzdCBjbGFzc2lmaWVyIHRvIGRldGVybWluZSB0aGUgbW9zdCBzaW1pbGFyIGhlYWx0aHkgZXF1aXZhbGVudCBvZiBhIGNlbGwuICAKCkF6aW11dGggcHJvdmlkZXMgY2VsbCB0eXBlIGxhYmVscyBhdCAyIGxldmVscywgc2VlIFtpbnRlcmFjdGl2ZSByZWZlcmVuY2VdKGh0dHBzOi8vYXppbXV0aC5odWJtYXBjb25zb3J0aXVtLm9yZy9yZWZlcmVuY2VzL2h1bWFuX2JvbmVtYXJyb3cvKS4KCkZvciB0aGlzIHN0ZXAsIHdlIGFyZSBub3QgaW50ZXJlc3RlZCBpbiB0aGUgZ2VuZXMgZGlzdGluZ3Vpc2hpbmcgdGhlIGRpZmZlcmVudCBjZWxsIHR5cGVzIGJlY2F1c2UgdGhleSBhcmUgYWxyZWFkeSB3ZWxsIGRlZmluZWQuIApTbyB3ZSBkb24ndCBuZWVkIGEgUkYgY2xhc3NpZmllciB3aGVyZSB3ZSBjYW4gZXh0cmFjdCBmZWF0dXJlIGltcG9ydGFuY2UgbWV0cmljcy4gCgpUaGUgbGFiZWwgdHJhbnNmZXIgaXMgdmVyeSBmYXN0IGFuZCBlYXN5IGFuZCBwcm92aWRlcyBhIHdlbGwgZXN0YWJsaXNoZWQgcmVmZXJlbmNlIGZyYW1ld29yayBvZiBjZWxsIHR5cGUgY2xhc3NpZmljYXRpb25zIGFuZCBoaWVyYXJjaGllcy4gCgpSZWZlcmVuY2UgZG93bmxvYWRlZCBmcm9tIFtoZXJlXShodHRwOi8vc2V1cmF0Lm55Z2Vub21lLm9yZy9zcmMvY29udHJpYi9ib25lbWFycm93cmVmLlNldXJhdERhdGFfMS4wLjAudGFyLmd6KS4KCmBgYHtyfQpsaWJyYXJ5KEF6aW11dGgpCmxpYnJhcnkoU2V1cmF0RGF0YSkKYGBgCgpgYGB7ciByZXN1bHRzPUYsIGV2YWw9Rn0KIyBUaGUgUnVuQXppbXV0aCBmdW5jdGlvbiBjYW4gdGFrZSBhIFNldXJhdCBvYmplY3QgYXMgaW5wdXQKIyBvdmVyd3JpdGVzIG1pc2NlbGxhbm91cyBkYXRhLCBzbyBzYXZpbmcgYXMgbmV3IG9iamVjdApnYWxlbl9hbWxfZG9ub3JzX2F6aW11dGggPC0gUnVuQXppbXV0aChnYWxlbl9hbWxfZG9ub3JzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWZlcmVuY2UgPSAiYXppbXV0aF9yZWZlcmVuY2UvYm9uZW1hcnJvd3JlZi5TZXVyYXREYXRhL2luc3QvYXppbXV0aC8iKQpgYGAKCkFkZCBBemltdXRoIGxhYmVscyB0byBvcmlnaW5hbCBzZXVyYXQgb2JqZWN0LgoKYGBge3IsIGV2YWw9Rn0KZ2FsZW5fYW1sX2Rvbm9ycyA8LQogIEFkZE1ldGFEYXRhKAogICAgZ2FsZW5fYW1sX2Rvbm9ycywKICAgIGdhbGVuX2FtbF9kb25vcnNfYXppbXV0aEBtZXRhLmRhdGEsCiAgICBjb2wubmFtZSA9IGMoInByZWRpY3RlZC5jZWxsdHlwZS5sMSIsICJwcmVkaWN0ZWQuY2VsbHR5cGUubDIiKQogICkKYGBgCgoKVGhlIGZ1bmN0aW9uIHJldHVybnMgY2VsbCB0eXBlIGxhYmVscyBvbiAyIGxldmVscywgYW5kIDIgUUMgbWV0cmljcywgYSBtYXBwaW5nIHNjb3JlIGFuZCBhIHByZWRpY3Rpb24gc2NvcmUgZm9yIGVhY2ggbGV2ZWwuICAKSXQgYWxzbyByZXR1cm5zIHRoZSBwcm9qZWN0aW9uIG9mIHRoZSBjZWxscyBvbnRvIHRoZSByZWZlcmVuY2UgVU1BUCBzcGFjZS4KCmBgYHtyIGZpZy5oZWlnaHQ9NywgZXZhbD1GfQpEaW1QbG90KGdhbGVuX2FtbF9kb25vcnNfYXppbXV0aCwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmNlbGx0eXBlLmwxIiwgc2h1ZmZsZSA9IFQsIGxhYmVsID0gVCkgKyBjb29yZF9lcXVhbCgpCkZlYXR1cmVQbG90KGdhbGVuX2FtbF9kb25vcnNfYXppbXV0aCwgZmVhdHVyZXMgPSAicHJlZGljdGVkLmNlbGx0eXBlLmwxLnNjb3JlIikgKyBjb29yZF9lcXVhbCgpCkRpbVBsb3QoZ2FsZW5fYW1sX2Rvbm9yc19hemltdXRoLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuY2VsbHR5cGUubDIiLCBzaHVmZmxlID0gVCwgbGFiZWwgPSBUKSArIGNvb3JkX2VxdWFsKCkKRmVhdHVyZVBsb3QoZ2FsZW5fYW1sX2Rvbm9yc19hemltdXRoLCBmZWF0dXJlcyA9ICJwcmVkaWN0ZWQuY2VsbHR5cGUubDIuc2NvcmUiKSArIGNvb3JkX2VxdWFsKCkKRmVhdHVyZVBsb3QoZ2FsZW5fYW1sX2Rvbm9yc19hemltdXRoLCBmZWF0dXJlcyA9ICJtYXBwaW5nLnNjb3JlIikgKyBjb29yZF9lcXVhbCgpCgpEaW1QbG90KGdhbGVuX2FtbF9kb25vcnNfYXppbXV0aCwgZ3JvdXAuYnkgPSAiU2FtcGxlQ2xhc3MiLCBzaHVmZmxlID0gVCkgKyBjb29yZF9lcXVhbCgpCkRpbVBsb3QoZ2FsZW5fYW1sX2Rvbm9yc19hemltdXRoLCBncm91cC5ieSA9ICJDZWxsVHlwZSIsIHNodWZmbGUgPSBULCBsYWJlbCA9IFQpICsgY29vcmRfZXF1YWwoKQpgYGAKCgpDb21wYXJlIEF6aW11dGggYW5ub3RhdGlvbnMgd2l0aCBiYWNrc3BpbiBjbHVzdGVyIGFubm90YXRpb24sIGZvbGxvd2VkIGJ5IFJGLgpBbHNvIGNvbXBhcmUgd2l0aCBtYWxpZ25hbnQvbm9uIG1hbGlnbmFudCBSRiBhbm5vdGF0aW9uLiAKCmBgYHtyIGZpZy5oZWlnaHQ9NSwgZXZhbD1GfQpoZWF0bWFwKHRhYmxlKGdhbGVuX2FtbF9kb25vcnMkYmFja3NwaW5fY2VsbHR5cGUsIGdhbGVuX2FtbF9kb25vcnMkcHJlZGljdGVkLmNlbGx0eXBlLmwxKSwgc2NhbGUgPSBOVUxMLCBSb3d2ID0gTkEsIENvbHYgPSBOQSkKaGVhdG1hcCh0YWJsZShnYWxlbl9hbWxfZG9ub3JzJGJhY2tzcGluX2NlbGx0eXBlLCBnYWxlbl9hbWxfZG9ub3JzJHByZWRpY3RlZC5jZWxsdHlwZS5sMiksIHNjYWxlID0gTlVMTCwgUm93diA9IE5BLCBDb2x2ID0gTkEpCgpoZWF0bWFwKHRhYmxlKGdhbGVuX2FtbF9kb25vcnMkQ2VsbFR5cGUsIGdhbGVuX2FtbF9kb25vcnMkcHJlZGljdGVkLmNlbGx0eXBlLmwxKSwgc2NhbGUgPSBOVUxMLCBSb3d2ID0gTkEsIENvbHYgPSBOQSkKaGVhdG1hcCh0YWJsZShnYWxlbl9hbWxfZG9ub3JzJENlbGxUeXBlLCBnYWxlbl9hbWxfZG9ub3JzJHByZWRpY3RlZC5jZWxsdHlwZS5sMiksIHNjYWxlID0gTlVMTCwgUm93diA9IE5BLCBDb2x2ID0gTkEpCmBgYAoKSW4gdGhlIGVuZCwgSSByZWFsaXplZCB0aGF0IHRoZSBBemltdXRoIGFubm90YXRpb25zIGFyZSBwcm9ibGVtYXRpYywgYmVjYXVlIGVpdGhlciB2ZXJ5IGdyYW51bGFyIG9yIHVzZWxlc3MgYmVjYXVzZSBsdW1waW5nIGV2ZXJ5dGhpbmcgaW50byBIU1BDLiAKCgojIyMgVHJhaW4gY2xhc3NpZmllcgoKYmFja3NwaW4gY2VsbHR5cGUgYW5ub3RhdGlvbiBpcyBhdmFpbGFibGUgZm9yIDc4MyBjZWxscyBvZiAxLDU5MCBCTTUgQ0QzNCtDRDM44oCTLgoKYGBge3J9CnkgPC0gZmFjdG9yKGdhbGVuX2FtbF9kb25vcnNfaGVhbHRoeSRiYWNrc3Bpbl9jZWxsdHlwZSkKWCA8LSBnYWxlbl9hbWxfZG9ub3JzX2hlYWx0aHlAYXNzYXlzJFJOQUBsYXllcnMkZGF0YQpYIDwtIFhbLCFpcy5uYSh5KV0Kcm93bmFtZXMoWCkgPC0gcm93bmFtZXMoZ2FsZW5fYW1sX2Rvbm9yc19oZWFsdGh5KQp5IDwtIHlbIWlzLm5hKHkpXQoKIyAjIGNvbWJpbmUgSFNDIGFuZCBQcm9nCiMgeVt5ICVpbiUgYygiSFNDIiwgIlByb2ciKV0gPC0gIkhTQ19Qcm9nXyIKYGBgCgpWYW4gR2FsZW4gZXQgYWwuIHNlbGVjdCBnZW5lcyB3aXRoIGF2ZXJhZ2Ugbm9ybWFsaXplZCBleHByZXNzaW9uID49IDAuMDEgKG5vcm1hbGl6ZWQgdG8gMTBrIHJlYWRzKS4KVGhpcyBpcyBiaWFzZWQgYnkgY2xhc3MuIEluc3RlYWQsIHNlbGVjdCBnZW5lcyBleHByZXNzZWQgaW4gNSUgY2VsbHMgaW4gYXQgbGVhc3QgMSBjbGFzcyAoMTAxNDEgZ2VuZXMpLgoKYGBge3J9CiMgbWVhbiBub3JtYWxpemVkIGV4cHJlc3Npb24gPj0gMC4wMQprZWVwLmdlbmVzIDwtIHJvd01lYW5zKGV4cChYKSAtIDEpID49IDAuMDEKdGFibGUoa2VlcC5nZW5lcykKCiMgZXhwcmVzc2VkIGluIDElIGNlbGxzCmtlZXAuZ2VuZXMuZXhwcmVzc2VkIDwtIHJvd1N1bXMoKFggPiAwKSAvIG5jb2woZ2FsZW5fYW1sX2Rvbm9yc19oZWFsdGh5KSkgPj0gMC4wMQp0YWJsZShrZWVwLmdlbmVzLmV4cHJlc3NlZCkKCiMgZXhwcmVzc2VkIGluIDElIGNlbGxzIGluIGF0IGxlYXN0IDEgY2xhc3MKY2xhc3NlcyA8LSB1bmlxdWUoeSkKa2VlcC5nZW5lcy5jbGFzcyA8LSBsYXBwbHkoY2xhc3NlcywgRlVOID0gZnVuY3Rpb24oY2xhc3MpIHsKICBnZW5lcyA8LSBuYW1lcyh3aGljaChyb3dTdW1zKChYWyx5ID09IGNsYXNzXSA+IDApIC8gc3VtKHkgPT0gY2xhc3MpKSA+PSAwLjA1KSkKICByZXR1cm4oZ2VuZXMpCn0pCmtlZXAuZ2VuZXMuY2xhc3MgPC0gcm93bmFtZXMoWCkgJWluJSB1bmxpc3Qoa2VlcC5nZW5lcy5jbGFzcykKdGFibGUoa2VlcC5nZW5lcy5jbGFzcykKClggPC0gWFt3aGljaChrZWVwLmdlbmVzLmNsYXNzKSxdCmBgYAoKCkFsbCBjZWxscyBhcmUgbWFsZS4gUmVtb3ZlIHNleC1zcGVjaWZpYyBnZW5lcyBhbnl3YXlzLiAKCmBgYHtyfQprZWVwLmdlbmVzIDwtICFyb3duYW1lcyhYKSAlaW4lIHNleF9nZW5lcwp0YWJsZShrZWVwLmdlbmVzKQpYIDwtIFhbd2hpY2goa2VlcC5nZW5lcyksXQpgYGAKCgpzYW1wc2l6ZSBpcyB0aGUgbnVtYmVyIG9mIGNlbGxzIHNhbXBsZWQgZnJvbSBlYWNoIGNsYXNzLiBCeSBkZWZhdWx0IGBjZWlsaW5nKC42MzIqbnJvdyh4KSlgLgpUaGlzIGlzIG9uZSB3YXkgdG8gZGVhbCB3aXRoIGltYmFsYW5jZWQgZGF0YSBpbiBSRiBieSB1c2luZyBiYWxhbmNlZCBkYXRhIHNldHMgZm9yIGVhY2ggdHJlZSwgZXZlbiBpZiB0aGUgZGF0YSBpcyBub3QgYmFsYW5jZWQsIHdoaWNoIGlzIG1hZGUgcG9zc2libGUgYnkgYm9vdHN0cmFwLgoKVHJhaW4gMTAwIHRyZWVzIGJlY2F1c2UgSSdtIGltcGF0aWVudC4gCgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQpyZi5jZWxsdHlwZS5vdXRlciA8LSByYW5kb21Gb3Jlc3QoCiAgeCA9IHQoWCksCiAgeSA9IHksCiAgc2FtcHNpemUgPSByZXAoNTAsIGxlbmd0aCh1bmlxdWUoeSkpKSwKICBudHJlZSA9IDIwMCwKICBkby50cmFjZSA9IEYKKQpgYGAKCmBgYHtyfQpyZi5jZWxsdHlwZS5vdXRlcgpgYGAKClNlbGVjdCAxayBmZWF0dXJlcyBmcm9tIDE0MDc0IGdlbmVzLiAKCmJlc3R2YXIgaXMgdGhlIHZhcmlhYmxlIHVzZWQgdG8gc3BsaXQgdGhlIG5vZGUgKDAgaWYgbm9kZSBpcyB0ZXJtaW5hbCkuCgpgYGB7cn0KcmYuY2VsbHR5cGUub3V0ZXIudXNlZDFrIDwtCiAgbmFtZXMoc29ydCh0YWJsZSgKICAgIHJvd25hbWVzKHJmLmNlbGx0eXBlLm91dGVyJGltcG9ydGFuY2UpW3JmLmNlbGx0eXBlLm91dGVyJGZvcmVzdCRiZXN0dmFyW3JmLmNlbGx0eXBlLm91dGVyJGZvcmVzdCRiZXN0dmFyICE9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDBdXQogICksIGRlY3JlYXNpbmcgPSBUUlVFKVsxOjEwMDBdKQoKcGxvdChyZi5jZWxsdHlwZS5vdXRlciRpbXBvcnRhbmNlW3JmLmNlbGx0eXBlLm91dGVyLnVzZWQxaywgMV0pICAjIGNvcnJlbGF0ZXMgdG8gaW1wb3J0YW5jZSBtZWFzdXJlCmBgYAoKCmBgYHtyfQpzZXQuc2VlZCgxMjMpCnJmLmNlbGx0eXBlLmlubmVyIDwtIHJhbmRvbUZvcmVzdCgKICB4ID0gdChYW3JmLmNlbGx0eXBlLm91dGVyLnVzZWQxayxdKSwKICB5ID0geSwKICBzYW1wc2l6ZSA9IHJlcCg1MCwgbGVuZ3RoKHVuaXF1ZSh5KSkpLAogIG50cmVlID0gMjAwLAogIGRvLnRyYWNlID0gRgopCmBgYAoKYGBge3J9CnJmLmNlbGx0eXBlLmlubmVyCmBgYAoKYGBge3J9CnlfcHJlZF9oZWFsdGh5IDwtIHByZWRpY3QocmYuY2VsbHR5cGUuaW5uZXIsIG5ld2RhdGEgPSB0KFgpLCB0eXBlID0gInByb2IiKQpgYGAKCmBgYHtyfQpyZi5jZWxsdHlwZS5pbm5lciRjb25mdXNpb24KYGBgCgplYXJseUVyeSBtaXNjbGFzc2lmaWVkIGFzIGxhdGVFcnkgYW5kIFByb2cuClByb2cgbWlzY2xhc3NpZmllZCBhcyBIU0MuCgpgYGB7cn0KaGVhdG1hcChyZi5jZWxsdHlwZS5pbm5lciRjb25mdXNpb24sIFJvd3YgPSBOQSwgQ29sdiA9IE5BLCBzY2FsZSA9IE5VTEwpCgpyZi5jZWxsdHlwZS5pbm5lciRjb25mdXNpb24gJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigiY2xhc3MiKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBjbGFzcywgeSA9IGNsYXNzLmVycm9yKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKQpgYGAKClByZWRpY3QgY2VsbHR5cGUgb2YgbWFsaWduYW50IEFNTCBjZWxscy4gCgoiSW4gZm91ciBwYXRpZW50cyAoQU1MMzE0LCBBTUwzNzEsIEFNTDcyMkIgYW5kIEFNTDk5NyksIGZvciB3aGljaCB3ZSBkZXRlY3RlZCBmZXcgbXV0YW50IHRyYW5zY3JpcHRzIGFuZCBmZXcgaGlnaCBxdWFsaXR5IGNlbGxzLCB3ZSBjb3VsZCBub3QgY29uZmlkZW50bHkgYXNzaWduIG1hbGlnbmFudCBjZWxscy4iCgpJIGFsc28gZXhjbHVkZSBBTUw0NzUgYW5kIEFNTDQyMEIgYmVjYXVzZSB0aGV5IGFsc28gb25seSBoYXZlIDcgYW5kIDYgY2VsbHMgZ2Vub3R5cGVkIGFzIG1hbGlnbmFudCAoYW5kIHVwb24gZmlyc3QgY2xhc3NpZmljYXRpb24sIHRob3NlIGNlbGxzIHdlcmUgbHltcGhvaWQgY2VsbHMpLgoKYGBge3J9CmdhbGVuX2FtbF9kb25vcnNfbWFsaWduYW50IDwtIHN1YnNldChnYWxlbl9hbWxfZG9ub3JzLCBzdWJzZXQgPSAoZ2Vub3R5cGUgPT0gIm1hbGlnbmFudCIgJiBTYW1wbGVDbGFzcyAhPSAiSGVhbHRoeURvbm9yIikpCnRhYmxlKGdhbGVuX2FtbF9kb25vcnNfbWFsaWduYW50JFNhbXBsZSkKCnNlbGVjdGVkX3NhbXBsZXMgPC0KICBkb25vcl9tZXRhZGF0YSRTYW1wbGVbIWRvbm9yX21ldGFkYXRhJFNhbXBsZSAlaW4lIGMoIkFNTDMxNCIsICJBTUwzNzEiLCAiQU1MNzIyQiIsICJBTUw5OTciLCAiQU1MNDc1IiwgIkFNTDQyMEIiKV0KZ2FsZW5fYW1sX2Rvbm9yc19tYWxpZ25hbnQgPC0KICBzdWJzZXQoZ2FsZW5fYW1sX2Rvbm9yc19tYWxpZ25hbnQsIFNhbXBsZSAlaW4lIHNlbGVjdGVkX3NhbXBsZXMpCmBgYAoKCmBgYHtyfQpYX21hbGlnbmFudCA8LSBnYWxlbl9hbWxfZG9ub3JzX21hbGlnbmFudEBhc3NheXMkUk5BQGxheWVycyRkYXRhCnJvd25hbWVzKFhfbWFsaWduYW50KSA8LSByb3duYW1lcyhnYWxlbl9hbWxfZG9ub3JzX21hbGlnbmFudCkKWF9tYWxpZ25hbnQgPC0gWF9tYWxpZ25hbnRbcmYuY2VsbHR5cGUub3V0ZXIudXNlZDFrLF0KCnlfcHJlZF9tYWxpZ25hbnQgPC0gcHJlZGljdChyZi5jZWxsdHlwZS5pbm5lciwgbmV3ZGF0YSA9IHQoWF9tYWxpZ25hbnQpLCB0eXBlID0gInByb2IiKQoKeV9wcmVkX21heCA8LSBjb2xuYW1lcyh5X3ByZWRfbWFsaWduYW50KVttYXguY29sKHlfcHJlZF9tYWxpZ25hbnQpXQpgYGAKCgpUaGVyZSBhcmUgY2VsbHMgY2xhc3NpZmllZCBhcyBseW1waG9jeXRlcywgZGVzcGl0ZSBkZXRlY3Rpb24gb2YgbXV0YXRlZCB0cmFuc2NyaXB0cy4KU2FtZSBmb3IgZWFybHkgZXJ5dGhyb2N5dGVzLCBwREMuIAoKYGBge3J9CnRhYmxlKHlfcHJlZF9tYXgpCnRhYmxlKHlfcHJlZF9tYXgsIGdhbGVuX2FtbF9kb25vcnNfbWFsaWduYW50JFNhbXBsZSkKdChyb3VuZCh0KAogIHRhYmxlKHlfcHJlZF9tYXgsIGdhbGVuX2FtbF9kb25vcnNfbWFsaWduYW50JFNhbXBsZSkKKSAvIGFzLnZlY3Rvcihjb2xTdW1zKAogIHRhYmxlKHlfcHJlZF9tYXgsIGdhbGVuX2FtbF9kb25vcnNfbWFsaWduYW50JFNhbXBsZSkKKSksIDIpKQpgYGAKCgpUaGVyZSBhcmUgc29tZSByYXRoZXIgdW5leHBlY3RlZCBIU1BDIGNlbGwgdHlwZSBwcmVkaWN0aW9uczogZWFybHkgZXJ5dGhyb2lkIGNlbGxzIGFuZCBwREMgY2VsbHMuIFdoaWxlIGx5bXBob2lkIGNlbGwgdHlwZXMgYXJlIHByZXR0eSBzdXJlbHkgYSBtaXNjbGFzc2lmaWNhdGlvbiwgZWFybHkgZXJ5IGFuZCBwREMgY2FuIGFjdHVhbGx5IGJlIG11dGF0ZWQgaW4gQU1MLCBhY2NvcmRpbmcgdG8gbGl0ZXJhdHVyZS4gCgpodHRwczovL3d3dy5tZHBpLmNvbS8yMDcyLTY2OTQvMTQvMTQvMzM3NQpBY3V0ZSBteWVsb2lkIGxldWtlbWlhIChBTUwpIHdpdGgg4omlMiUgcGxhc21hY3l0b2lkIGRlbmRyaXRpYyBjZWxscyAocERDKSBoYXMgYmVlbiByZWNlbnRseSBkZXNjcmliZWQgYXMgQU1MIHdpdGggcERDIGRpZmZlcmVudGlhdGlvbiAocERDLUFNTCkgY2hhcmFjdGVyaXplZCBieSBwREMgZXhwYW5zaW9uIHdpdGggZnJlcXVlbnQgUlVOWDEgbXV0YXRpb25zLgoKQU1MOTIxQSBoYXMgOCUgY2VsbHMgcHJlZGljdGVkIGFzIHBEQyBhbmQgUlVOWDEgTk1fMDAxNzU0IGMuMTY3VD5DIHAuTDU2UyAoNjMuNSUsIFZVUykKCkluIGNvbWJpbmF0aW9uIHdpdGggdGhlIHBvc3NpYmxlIHNpZ25hdHVyZSBjb21iaW5hdGlvbnMgaW4gQU1MIGNlbGxzLCBJIHdhbnQgdG8ga2VlcCB0aG9zZSBwcmVkaWN0aW9ucy4gCgpIb3dldmVyLCB0aGVyZSBhcmUgdG9vIGZldyBjZWxscyB0byB1c2UgdGhlbSBhcyBjbGFzc2VzIGluIGEgY2xhc3NpZmllci4gSW5zdGVhZCwgSSdsbCBpbmNsdWRlIHRoZW0gYXMgZmVhdHVyZXMsIGFsb25nc2lkZSBnZW5lcywgYW5kIHByZWRpY3QgbWFsaWduYW50IHZzLiBub3JtYWwgY2xhc3Nlcy4gCgpJJ2xsIG9ubHkgdHJhaW4gMSBjbGFzc2lmaWVyLCBpbnN0ZWFkIG9mIGlubmVyIGFuZCBvdXRlci4gClRoaXMgd2F5LCB2YWxpZGF0aW9uIG9mIGNvcnJlY3QgcHJlZGljdGlvbiBvZiBtYWxpZ25hbnQgY2VsbHMgaXMgbW9yZSB0cnV0aGZ1bC4gCgpgYGB7cn0KWF9oZWFsdGh5IDwtIGdhbGVuX2FtbF9kb25vcnNfaGVhbHRoeUBhc3NheXMkUk5BQGxheWVycyRkYXRhClhfaGVhbHRoeSA8LSBYX2hlYWx0aHlbLCFpcy5uYShnYWxlbl9hbWxfZG9ub3JzX2hlYWx0aHkkYmFja3NwaW5fY2VsbHR5cGUpXQpyb3duYW1lcyhYX2hlYWx0aHkpIDwtIHJvd25hbWVzKGdhbGVuX2FtbF9kb25vcnNfaGVhbHRoeSkKIyBjb21iaW5lIGNlbGx0eXBlIHByZWRpY3Rpb24gd2l0aCBnZW5lIGV4cHJlc3Npb24KWF9oZWFsdGh5X2NlbGx0eXBlIDwtIHJiaW5kKFhfaGVhbHRoeSwgdCh5X3ByZWRfaGVhbHRoeSkpCgpYX21hbGlnbmFudCA8LSBnYWxlbl9hbWxfZG9ub3JzX21hbGlnbmFudEBhc3NheXMkUk5BQGxheWVycyRkYXRhCnJvd25hbWVzKFhfbWFsaWduYW50KSA8LSByb3duYW1lcyhnYWxlbl9hbWxfZG9ub3JzX21hbGlnbmFudCkKIyBjb21iaW5lIGNlbGx0eXBlIHByZWRpY3Rpb24gd2l0aCBnZW5lIGV4cHJlc3Npb24KWF9tYWxpZ25hbnRfY2VsbHR5cGUgPC0gcmJpbmQoWF9tYWxpZ25hbnQsIHQoeV9wcmVkX21hbGlnbmFudCkpCgpYX2NlbGx0eXBlIDwtIGNiaW5kKFhfaGVhbHRoeV9jZWxsdHlwZSwgWF9tYWxpZ25hbnRfY2VsbHR5cGUpCmRpbShYX2NlbGx0eXBlKQoKWCA8LSBjYmluZChYX2hlYWx0aHksIFhfbWFsaWduYW50KQpkaW0oWCkKCnkgPSBmYWN0b3IoYyhyZXAoImhlYWx0aHkiLCBuY29sKFhfaGVhbHRoeSkpLCByZXAoIm1hbGlnbmFudCIsIG5jb2woWF9tYWxpZ25hbnQpKSkpCnRhYmxlKHkpCmBgYAoKS2VlcCBjZWxscyBleHByZXNzZWQgaW4gPj0gMSUgaW4gYXQgbGVhc3Qgb25lIG9mIHRoZSBjbGFzc2VzLiAKCmBgYHtyfQprZWVwLmdlbmVzLmV4cHJlc3NlZCA8LSByb3dTdW1zKChYID4gMCkgLyBuY29sKFgpKSA+PSAwLjAxCnRhYmxlKGtlZXAuZ2VuZXMuZXhwcmVzc2VkKQoKIyBleHByZXNzZWQgaW4gMSUgY2VsbHMgaW4gYXQgbGVhc3QgMSBjbGFzcwpjbGFzc2VzIDwtIHVuaXF1ZSh5KQprZWVwLmdlbmVzLmNsYXNzIDwtIGxhcHBseShjbGFzc2VzLCBGVU4gPSBmdW5jdGlvbihjbGFzcykgewogIGdlbmVzIDwtIG5hbWVzKHdoaWNoKHJvd1N1bXMoKFhbLHkgPT0gY2xhc3NdID4gMCkgLyBzdW0oeSA9PSBjbGFzcykpID49IDAuMDEpKQogIHJldHVybihnZW5lcykKfSkKa2VlcC5nZW5lcy5jbGFzcyA8LSByb3duYW1lcyhYKSAlaW4lIHVubGlzdChrZWVwLmdlbmVzLmNsYXNzKQpuYW1lcyhrZWVwLmdlbmVzLmNsYXNzKSA8LSByb3duYW1lcyhYKQp0YWJsZShrZWVwLmdlbmVzLmNsYXNzKQpgYGAKCmBgYHtyfQpYIDwtIFhbbmFtZXMod2hpY2goa2VlcC5nZW5lcy5jbGFzcykpLF0KWF9jZWxsdHlwZSA8LSBYX2NlbGx0eXBlW2MobmFtZXMod2hpY2goa2VlcC5nZW5lcy5jbGFzcykpLCBjb2xuYW1lcyh5X3ByZWRfbWFsaWduYW50KSksIF0KWF9jZWxsdHlwZV9kcml2ZXIgPC0gWF9jZWxsdHlwZVshcm93bmFtZXMoWF9jZWxsdHlwZSkgJWluJSBnZW5lc19nZW5vdHlwZXMsIF0KYGBgCgpUcmFpbiAxIGNsYXNzaWZpZXIgd2l0aCBjZWxsdHlwZSBwcm9iYWJpbGl0aWVzIGFzIGZlYXR1cmVzIGFuZCBvbmUgd2l0aG91dCBhbmQgb25lIHdpdGhvdXQgZ2VuZXMgd2l0aCBkcml2ZXIgbXV0YXRpb25zLiAKCmBgYHtyfQpzZXQuc2VlZCgxMjMpCnJmLm1hbGlnbmFudCA8LSByYW5kb21Gb3Jlc3QoCiAgeCA9IHQoWCksCiAgeSA9IHksCiAgc2FtcHNpemUgPSByZXAoMjAwLCBsZW5ndGgodW5pcXVlKHkpKSksCiAgbnRyZWUgPSAyMDAsCiAgZG8udHJhY2UgPSBGCikKYGBgCgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQpyZi5tYWxpZ25hbnQuY2VsbHR5cGUgPC0gcmFuZG9tRm9yZXN0KAogIHggPSB0KFhfY2VsbHR5cGUpLAogIHkgPSB5LAogIHNhbXBzaXplID0gcmVwKDIwMCwgbGVuZ3RoKHVuaXF1ZSh5KSkpLAogIG50cmVlID0gMjAwLAogIGRvLnRyYWNlID0gRgopCmBgYAoKYGBge3J9CnNldC5zZWVkKDEyMykKcmYubWFsaWduYW50LmNlbGx0eXBlLmRyaXZlciA8LSByYW5kb21Gb3Jlc3QoCiAgeCA9IHQoWF9jZWxsdHlwZV9kcml2ZXIpLAogIHkgPSB5LAogIHNhbXBzaXplID0gcmVwKDIwMCwgbGVuZ3RoKHVuaXF1ZSh5KSkpLAogIG50cmVlID0gMjAwLAogIGRvLnRyYWNlID0gRgopCmBgYAoKSW5jbHVkaW5nIGNlbGwgdHlwZSBwcm9iYWJpbGl0aWVzIGdpdmVzIGEgCgpgYGB7cn0KcmYubWFsaWduYW50CnJmLm1hbGlnbmFudC5jZWxsdHlwZQpyZi5tYWxpZ25hbnQuY2VsbHR5cGUuZHJpdmVyCmBgYAoKYGBge3J9CnlfbWFsaWduYW50X2NlbGx0eXBlX3ByZWQgPC0gcHJlZGljdChyZi5tYWxpZ25hbnQuY2VsbHR5cGUsIG5ld2RhdGEgPSB0KFhfY2VsbHR5cGUpLCB0eXBlID0gInByb2IiKQpgYGAKCmBgYHtyfQp5X21hbGlnbmFudF9jZWxsdHlwZV9wcmVkX21heCA8LSBjb2xuYW1lcyh5X21hbGlnbmFudF9jZWxsdHlwZV9wcmVkKVttYXguY29sKHlfbWFsaWduYW50X2NlbGx0eXBlX3ByZWQpXQoKZG9ub3JfbGlzdCA8LSB1bmlxdWUoZ2FsZW5fYW1sX2Rvbm9yc19tYWxpZ25hbnQkU2FtcGxlKQpuYW1lcyhkb25vcl9saXN0KSA8LSBkb25vcl9saXN0CgojIGRvbm9yX2FjY3VyYWN5X3JmLm1hbGlnbmFudC5jZWxsdHlwZSA8LSBsYXBwbHkoZG9ub3JfbGlzdCwgZnVuY3Rpb24oZG9ub3IpIHN1bSh5X21hbGlnbmFudF9jZWxsdHlwZV9wcmVkX21heFtkb25vcnMgPT0gZG9ub3JdID09ICJtYWxpZ25hbnQiKSAvIHN1bShkb25vcnMgPT0gZG9ub3IpKQojIGRvbm9yX2FjY3VyYWN5X3JmLm1hbGlnbmFudC5jZWxsdHlwZQpgYGAKCgojIyMgTW9kZWwgZXZhbHVhdGlvbgoKIyMjIyBDcm9zcy12YWxpZGF0aW9uIG9uIEFNTCBkb25vcnMKClBpY2sgNCBkb25vcnMgYXQgcmFuZG9tICh0byBzYXZlIHRpbWUpLgoKQWNjdXJhY3kgaXMgaG93IG1hbnkgY2VsbHMgYXJlIGNvcnJlY3RseSBjbGFzc2lmaWVkIGFzIG1hbGlnbmFudC4gCgpXaXRoIHRoaXMgY3Jvc3MgdmFsaWRhdGlvbiwgd2UgY291bGQgZG8gbW9yZSBwYXJhbXRlciB0ZXN0aW5nLiBMaWtlIHRoZSBudW1iZXIgb2YgZmVhdHVyZXMgdG8gcGljayBmb3IgdGhlIGlubmVyIGNlbGwgdHlwZSBjbGFzc2lmaWVyLgpPciBob3cgbWFueSBwYXJhbXRlcnMgdG8gc2FtcGxlIGZvciBlYWNoIGRlY2lzaW9uIGluIHRoZSB0cmVlLiBPciB0aGUgZGVwdGggb2YgdGhlIHRyZWVzLiAKCmBgYHtyfQpkb25vcnMgPC0gYyhnYWxlbl9hbWxfZG9ub3JzX2hlYWx0aHkkU2FtcGxlLCBnYWxlbl9hbWxfZG9ub3JzX21hbGlnbmFudCRTYW1wbGUpCnRhYmxlKGRvbm9ycykKZG9ub3JzX2N2IDwtIHVuaXF1ZShnYWxlbl9hbWxfZG9ub3JzX21hbGlnbmFudCRTYW1wbGUsIGdhbGVuX2FtbF9kb25vcnNfaGVhbHRoeSRTYW1wbGUpCnNldC5zZWVkKDEyMykKZG9ub3JzX2N2IDwtIHNhbXBsZShkb25vcnNfY3YsIHNpemUgPSA0KQpkb25vcnNfY3YKYGBgCgpgYGB7cn0KY3ZfcmVzdWx0cyA8LSBsYXBwbHkoZG9ub3JzX2N2LCBmdW5jdGlvbihkb25vcikgewogIGNhdChkb25vcikKICBjYXQoIlxuIikKICAKICAjIHRyYWluIGFuZCB2YWxpZGF0aW9uIHNwbGl0CiAgWF9jZWxsdHlwZV9jdl90cmFpbiA8LSBYX2NlbGx0eXBlWywgZG9ub3JzICE9IGRvbm9yXQogIFhfY2VsbHR5cGVfY3ZfdGVzdCA8LSBYX2NlbGx0eXBlWywgZG9ub3JzID09IGRvbm9yXQogIHlfY3ZfdHJhaW4gPC0geVtkb25vcnMgIT0gZG9ub3JdCiAgeV9jdl90ZXN0IDwtIHlbZG9ub3JzID09IGRvbm9yXQogIAogICMgdHJhaW4gb24gdGVzdCBkYXRhCiAgc2V0LnNlZWQoMTIzKQogIHJmLm1hbGlnbmFudC5jZWxsdHlwZS5jdiA8LSByYW5kb21Gb3Jlc3QoCiAgICB4ID0gdChYX2NlbGx0eXBlX2N2X3RyYWluKSwKICAgIHkgPSB5X2N2X3RyYWluLAogICAgc2FtcHNpemUgPSByZXAoMjAwLCBsZW5ndGgodW5pcXVlKHlfY3ZfdHJhaW4pKSksCiAgICBudHJlZSA9IDIwMCwKICAgIGRvLnRyYWNlID0gRkFMU0UKICApCiAgCiAgIyBwcmVkaWN0IHZhbGlkYXRpb24gZGF0YQogIHlfY3ZfcHJlZCA8LQogICAgcHJlZGljdCgKICAgICAgcmYubWFsaWduYW50LmNlbGx0eXBlLmN2LAogICAgICBuZXdkYXRhID0gdChYX2NlbGx0eXBlX2N2X3Rlc3QpLAogICAgICB0eXBlID0gInByb2IiCiAgICApCiAgeV9jdl9wcmVkX21heCA8LSBjb2xuYW1lcyh5X2N2X3ByZWQpW21heC5jb2woeV9jdl9wcmVkKV0KICAKICAjIGNhbGN1bGF0ZSBhY2N1cmFjeQogIGFjY3VyYWN5IDwtIHN1bSh5X2N2X3ByZWRfbWF4ID09IHlfY3ZfdGVzdCkgLyBsZW5ndGgoeV9jdl9wcmVkX21heCkKICByZXR1cm4oYWNjdXJhY3kpCn0pCgpuYW1lcyhjdl9yZXN1bHRzKSA8LSBkb25vcnNfY3YKYGBgCgpDcm9zcyB2YWxpZGF0aW9uIHdoZW4gbGVhdmluZyBvdXQgZHJpdmVyIG11dGF0aW9uIGdlbmVzLiAKCmBgYHtyfQpjdl9yZXN1bHRzX2RyaXZlciA8LSBsYXBwbHkoZG9ub3JzX2N2LCBmdW5jdGlvbihkb25vcikgewogIGNhdChkb25vcikKICBjYXQoIlxuIikKICAKICAjIHRyYWluIGFuZCB2YWxpZGF0aW9uIHNwbGl0CiAgWF9jZWxsdHlwZV9jdl90cmFpbiA8LSBYX2NlbGx0eXBlX2RyaXZlclssIGRvbm9ycyAhPSBkb25vcl0KICBYX2NlbGx0eXBlX2N2X3Rlc3QgPC0gWF9jZWxsdHlwZV9kcml2ZXJbLCBkb25vcnMgPT0gZG9ub3JdCiAgeV9jdl90cmFpbiA8LSB5W2Rvbm9ycyAhPSBkb25vcl0KICB5X2N2X3Rlc3QgPC0geVtkb25vcnMgPT0gZG9ub3JdCiAgCiAgIyB0cmFpbiBvbiB0ZXN0IGRhdGEKICBzZXQuc2VlZCgxMjMpCiAgcmYubWFsaWduYW50LmNlbGx0eXBlLmN2IDwtIHJhbmRvbUZvcmVzdCgKICAgIHggPSB0KFhfY2VsbHR5cGVfY3ZfdHJhaW4pLAogICAgeSA9IHlfY3ZfdHJhaW4sCiAgICBzYW1wc2l6ZSA9IHJlcCgyMDAsIGxlbmd0aCh1bmlxdWUoeV9jdl90cmFpbikpKSwKICAgIG50cmVlID0gMjAwLAogICAgZG8udHJhY2UgPSBGQUxTRQogICkKICAKICAjIHByZWRpY3QgdmFsaWRhdGlvbiBkYXRhCiAgeV9jdl9wcmVkIDwtCiAgICBwcmVkaWN0KAogICAgICByZi5tYWxpZ25hbnQuY2VsbHR5cGUuY3YsCiAgICAgIG5ld2RhdGEgPSB0KFhfY2VsbHR5cGVfY3ZfdGVzdCksCiAgICAgIHR5cGUgPSAicHJvYiIKICAgICkKICB5X2N2X3ByZWRfbWF4IDwtIGNvbG5hbWVzKHlfY3ZfcHJlZClbbWF4LmNvbCh5X2N2X3ByZWQpXQogIAogICMgY2FsY3VsYXRlIGFjY3VyYWN5CiAgYWNjdXJhY3kgPC0gc3VtKHlfY3ZfcHJlZF9tYXggPT0geV9jdl90ZXN0KSAvIGxlbmd0aCh5X2N2X3ByZWRfbWF4KQogIHJldHVybihhY2N1cmFjeSkKfSkKCm5hbWVzKGN2X3Jlc3VsdHNfZHJpdmVyKSA8LSBkb25vcnNfY3YKYGBgCgpgYGB7cn0KZG9ub3JfbWV0YWRhdGEgJT4lCiAgZmlsdGVyKFNhbXBsZSAlaW4lIGRvbm9yc19jdiwgYERheXMgZnJvbSBkaWFnbm9zaXNgID09ICJEMCIpICU+JQogIHNlbGVjdChTYW1wbGUsIGBSSFAgTXV0YXRpb25zYCwgYENvbW1vbiB0cmFuc2xvY2F0aW9uYCkgJT4lCiAgdW5pcXVlKCkKYGBgCgpgYGB7cn0KbGFwcGx5KGN2X3Jlc3VsdHMsIHJvdW5kLCAzKQpgYGAKCmBgYHtyfQpsYXBwbHkoY3ZfcmVzdWx0c19kcml2ZXIsIHJvdW5kLCAzKQpgYGAKCgpFeGNsdWRpbmcgZ2VuZXMgd2l0aCBkcml2ZXIgbXV0YXRpb25zIGluY3JlYXNlcyBwYXRpZW50IENWIGFjY3VyYWN5IHNsaWdodGx5IGluIDMvNCBjYXNlcy4KCgoKIyMjIyBDcm9zcy12YWxpZGF0aW9uIG9uIGRyaXZlciBtdXRhdGlvbnMKClRPRE8KClRvIHRlc3QgdGhlIGdlbmVyYWxpemFiaWxpdHkgb2YgdGhlIG1vZGVsIG9uIHVuc2VlbiBtdXRhdGlvbnMsIGNyb3NzIHZhbGlkYXRpb24gc2hvdWxkIGJlIHBlcmZvcm1lZCB3aXRoIGhvbGRpbmcgb3V0IGFsbCBzYW1wbGVzIHdpdGggYSBzcGVjaWZpYyBkcml2ZXIgbXV0YXRpb24uIAoKIlRvIGV4Y2x1ZGUgdGhlIHBvc3NpYmlsaXR5IHRoYXQgdGhlIGhpZ2ggZnJlcXVlbmN5IG9mIGNlbGxzIHdpdGggZGV0ZWN0ZWQgTlBNMSBtdXRhdGlvbnMgYWZmZWN0ZWQgdGhlIGNsYXNzaWZpZXIsIHdlIGdlbmVyYXRlZCBhIHNlcGFyYXRlIGNsYXNzaWZpZXIgdGhhdCBkb2VzIG5vdCBjb25zaWRlciBOUE0xIG11dGFudCBjYWxscy4gVGhpcyBzZXBhcmF0ZSBjbGFzc2lmaWVyIGhhZCBlcXVhbGx5IGhpZ2ggc3BlY2lmaWNpdHkgKDk5LjglIG9mIG5vcm1hbCBjZWxscyBjb3JyZWN0bHkgY2FsbGVkIG5vcm1hbCksIGFuZCBzZW5zaXRpdml0eSAoOTMlIG9mIG1hbGlnbmFudCBjZWxscyBjb3JyZWN0bHkgY2FsbGVkIG1hbGlnbmFudCkgaW4gNS1mb2xkIGNyb3NzLXZhbGlkYXRpb24uIEl0IGlzIGFsc28gY29uc2lzdGVudCB3aXRoIHRoZSBvcmlnaW5hbCBjbGFzc2lmaWVyOiA5NyUgb2YgY2VsbHMgb3JpZ2luYWxseSBjbGFzc2lmaWVkIGFzIG5vcm1hbCB3ZXJlIGNsYXNzaWZpZWQgYXMgbm9ybWFsOyA5MSUgb2YgY2VsbHMgb3JpZ2luYWxseSBjbGFzc2lmaWVkIGFzIG1hbGlnbmFudCB3ZXJlIGNsYXNzaWZpZWQgYXMgbWFsaWduYW50LiBUaGVzZSByZXN1bHRzIGluZGljYXRlIHRoYXQgdGhlIGNsYXNzaWZpZXIgaXMgcm9idXN0IHRvIHRoZSBmcmVxdWVuY3kgb2YgTlBNMSBtdXRhdGlvbnMgaW4gdGhlIHRyYWluaW5nIHNldC4iCgpTaG91bGQgaGF2ZSBwdXQgdGhlIE5QTTEgc2FtcGxlIGluIHRoZSBub24tTlBNMSB0cmFpbmVkIGNsYXNzaWZpZXIuCgojIyMjIEV4dGVybmFsIHRlc3QgZGF0YXNldAoKc2NSTkEtc2VxIGZyb20gQU1MIHBhdGllbnRzIChOUE0xKSB3aXRoIHNjIGdlbm90eXBpbmcuCgpOYWxkaW5pLCBNLk0uLCBDYXNpcmF0aSwgRy4sIEJhcmNlbGxhLCBNLiBldCBhbC4gTG9uZ2l0dWRpbmFsIHNpbmdsZS1jZWxsIHByb2ZpbGluZyBvZiBjaGVtb3RoZXJhcHkgcmVzcG9uc2UgaW4gYWN1dGUgbXllbG9pZCBsZXVrZW1pYS4gTmF0IENvbW11biAxNCwgMTI4NSAoMjAyMykuIFtodHRwczovL2RvaS5vcmcvMTAuMTAzOC9zNDE0NjctMDIzLTM2OTY5LTBdKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvczQxNDY3LTAyMy0zNjk2OS0wKQoKMTAgcGF0aWVudHMgd2l0aCBOUE0xbXV0IEFNTCwgcHJlc2VudCBpbiB2YW4gR2FsZW4gZGF0YXNldC4KTWFqb3JpdHkgb2YgTlBNMW11dCBBTUwgc2FtcGVscyBpbiB2YW4gR2FsZW4gZGF0YXNldCBhbHNvIGhhdmUgRE5NVDMgbXV0YXRpb24uIEROTVQzIHN0YXR1cyBpcyBub3QgbWVudGlvbmVkIGZvciB0aGlzIHN0dWR5LiAKCjMgcGF0aWVudHMgd2l0aCBkZWwoNykgQU1MLCBub3QgcHJlc2VudCBpbiB2YW4gR2FsZW4gZGF0YS4KCipQcmVwcm9jZXNzaW5nIGluZm9ybWF0aW9uKgpGZWF0dXJlLWJhcmNvZGVzIGZpbHRlcmVkIG1hdHJpY2VzIGZyb20gQ2VsbCBSYW5nZXIgd2VyZSB1c2VkIGFzIGlucHV0IGZvciBTZXVyYXQgUiBwYWNrYWdlNjQsNjUgKHZlcnNpb24gMy4yLjMpLiBTZXVyYXQgb2JqZWN0cyB3ZXJlIG1lcmdlZCBpbiBhIHNpbmdsZSBmdWxsIGRhdGFzZXQuIENlbGxzIHdpdGggYSBtaXRvY2hvbmRyaWFsIGNvdW50IHJhdGlvIGhpZ2hlciB0aGFuIDAuMiBhbmQgPDEwMCBvciA+NzAwMCBleHByZXNzZWQgZ2VuZXMgd2VyZSByZW1vdmVkIGZyb20gdGhlIGRhdGFzZXQuIFVNSSBjb3VudHMgd2VyZSBsb2cgbm9ybWFsaXplZCBhbmQgc2NhbGVkIGZvciBhIGZhY3RvciBvZiAxMCwwMDAuICAKVGhlIHRvcCAyMCUgbW9zdCB2YXJpYWJsZSBnZW5lcyB3ZXJlIHNlbGVjdGVkIGZvciBkb3duc3RyZWFtIGFuYWx5c2lzLiBDZWxsIGN5Y2xlIHNjb3JlcyB3ZXJlIGFzc2lnbmVkIHdpdGggdGhlIENlbGxDeWNsZVNjb3JpbmcgZnVuY3Rpb24gdXNpbmcgYSByZWZlcmVuY2UgZ2VuZSBsaXN0czY2LiBXZSBzY2FsZWQgZGF0YSBhbmQgcmVncmVzc2VkIG91dCB1bndhbnRlZCB2YXJpYWJpbGl0eSBieSBwYXNzaW5nIFVNSSBjb3VudCwgcGVyY2VudCBvZiBtaXRvY2hvbmRyaWFsIGdlbmVzIGFuZCBjZWxsIGN5Y2xlIGRpZmZlcmVuY2UgZGVmaW5lZCB2YXJpYWJsZXMgdG8gdGhlIHZhcnMudG8ucmVncmVzcyBhcmd1bWVudC4gQ2VsbCBjeWNsZSBkaWZmZXJlbmNlIHdhcyBkZWZpbmVkIGFzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gUyBwaGFzZSBhbmQgRy8yIE0gcGhhc2UgbW9kdWxlIHNjb3Jlcy4gRG93bnN0cmVhbSBhbmFseXNpcyB3YXMgcGVyZm9ybWVkIG9uIHRoZSB0b3AgMTAwIHByaW5jaXBhbCBjb21wb25lbnRzIChQQ0EpLiBJbiBvcmRlciB0byByZWR1Y2UgcGF0aWVudC1yZWxhdGVkIGFuZCAxMHggY2hlbWlzdHJ5IHZlcnNpb24gKHYyIHZzIHYzKSBiaWFzLCB3ZSBwZXJmb3JtZWQgZGF0YSBpbnRlZ3JhdGlvbiB1c2luZyB0aGUgSGFybW9ueSBwYWNrYWdlICh2MS4wKTI0LgoKKk11dGF0aW9uIGFubm90YXRpb24gaW5mb3JtYXRpb24qCk5QTTEtTUYgY29uc2lkZXJzIHRoZSBVTUkgY291bnRzIHN1cHBvcnRpbmcgZWl0aGVyIE5QTTEgbXV0YW50IG9yIFdUIGFsbGVsZSB0byBjbGFzc2lmeSBjZWxscyBhcyAKTVVUICjiiaUxIFVNSSBmb3IgdGhlIG11dGFudCBhbGxlbGUpCldUICg+NSBXVCB0cmFuc2NyaXB0cywgbm8gbXV0YW50IHRyYW5zY3JpcHRzKQpORCDigJMgbm90IGRldGVjdGVkIChjZWxscyB3aXRoIOKJpCA1IFdUIHRyYW5zY3JpcHRzIGFuZCBubyBtdXRhbnQgdHJhbnNjcmlwdHMpCk5vQ2FsbCAoY2VsbHMgd2l0aG91dCBjb3ZlcmFnZSBvdmVyIHRoZSBOUE0xIG11dGF0aW9uIHJlZ2lvbikKCkNoZWNrIHBhdGllbnQgbWV0YWRhdGEuICAKRG8gbm90IHByb3ZpZGUgaW5mb3JtYXRpb24gb24gYWdlLCBTZXggb3IgY2xpbmluY2FsIGJsYXN0IGNvdW50LgoKYGBge3IgcmVzdWx0cz1GfQptZXRhZGF0YV9ucG0xX3BhdGllbnRzIDwtIHJlYWR4bDo6cmVhZF94bHN4KCJuYWxkaW5pX2V0X2FsLzQxNDY3XzIwMjNfMzY5NjlfTU9FU001X0VTTS54bHN4IikKYGBgCgoKYGBge3J9Cm1ldGFkYXRhX25wbTFfcGF0aWVudHMKYGBgCgoKQ2hlY2sgY2VsbCBtZXRhZGF0YS4gIApJIGNhbid0IGZpZ3VyZSBvdXQgd2hhdCB0aGUgb3JpZy5pZGVudCAoZS5nLiBNMDMpIGlzLiBJdCBpcyB0aGUgSUQgdXNlZCBpbiB0aGUgZXhwcmVzc2lvbiBtYXRyaXguIFNvIG1heWJlIHRoZSBjYXB0dXJlLCBidXQgaXQgZG9lc24ndCBtYWtlIG11Y2ggc2Vuc2UgdG8gaGF2ZSA5IGNlbGxzIGZyb20gUFQxMiBhbmQgNTcwIGNlbGxzIGZyb20gUFQxMyBpbiBNNDQuIFdoeSB3b3VsZCB0aGVyZSBiZSBsZWFrYWdlIGJldHdlZW4gY2FwdHVyZXMuIAoKUFQwMSwgUFQwMiwgUFQxMyBhcmUgcHJpbWFyeSByZWZyYWN0b3J5LiAKUFQwNiwgUFQwNywgUFQxMiBhcmUgbG9uZy10ZXJtIGNvbXBsZXRlIHJlbWlzc2lvbi4KUFQwOCwgUFQwOSwgUFQxMCwgUFQxNSBhcmUgTlBNMW11dCBBTUwgcmVsYXBzZSBwb3N0LWNoZW1vdGhlcmFweS4KCmBgYHtyfQptZXRhZGF0YV9ucG0xX2NlbGxzIDwtIHJlYWRSRFMoIm5hbGRpbmlfZXRfYWwvR1NFMTg1OTkxX0Z1bGxfUGF0aWVudF9tZXRhZGF0YS5yZHMiKQpoZWFkKG1ldGFkYXRhX25wbTFfY2VsbHMpCmBgYAoKQ2hlY2sgbnVtYmVyIG9mIGdlbm90eXBlZCBjZWxscyBwZXIgc2FtcGxlIHRvIHBpY2sgb25lIGZvciB0ZXN0aW5nLiAKClBUMDIsIGNvbWJpbmluZyBkaWFnbm9zaXMgYW5kIEQzMCBzYW1wbGVzLCBoYXMgYSBsb3Qgb2YgV1QgYW5kIE1VVCBjZWxscywgTlBNMW11dCwgcHJpbWFyeSByZWZyYWN0b3J5LiAKR29vZCBwYXRpZW50IHRvIHRlc3QgaWYgY2xhc3NpZmllciBjYW4gZXh0cmFwb2xhdGUgdG8gb3RoZXIgc2NSTkEtc2VxIGRhdGFzZXRzLgoKYGBge3J9Cm1ldGFkYXRhX25wbTFfcGF0aWVudHMgJT4lCiAgbXV0YXRlKFNhbXBsZSA9IHBhc3RlKFBhdGllbnRJRCwgVGltZXBvaW50LCBzZXAgPSAiXyIpKSAlPiUKICBncm91cF9ieShTYW1wbGUsIENsYXNzaWZpY2F0aW9uKSAlPiUKICBzdW1tYXJpemUobl9jZWxscyA9IHN1bShuKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gU2FtcGxlLCB5ID0gbl9jZWxscywgZmlsbCA9IENsYXNzaWZpY2F0aW9uKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUocGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkKYGBgCgpDYXB0dXJlcyB3aXRoIFBUMDIgZGF0YS4KTm90IHN1cmUgd2h5IHRoZXJlIGFyZSBvbmx5IDkgY2VsbHMgaW4gdGhlIG90aGVyIGNhcHR1cmUuIAoKYGBge3J9Cm1ldGFkYXRhX25wbTFfY2VsbHMgJT4lCiAgZmlsdGVyKFBhdGllbnRJRCA9PSAiUFQwMiIpICU+JQogIHB1bGwob3JpZy5pZGVudCkgJT4lCiAgdGFibGUoKQoKbWV0YWRhdGFfbnBtMV9jZWxscyAlPiUKICBmaWx0ZXIoUGF0aWVudElEID09ICJQVDA3IikgJT4lCiAgcHVsbChvcmlnLmlkZW50KSAlPiUKICB0YWJsZSgpCmBgYAoKUmVhZCBpbiBjb3VudCBkYXRhLgoKYGBge3J9Cm5hbGRpbmlfc2V1cmF0X20yNSA8LSBSZWFkMTBYKAogIGRhdGEuZGlyID0gIm5hbGRpbmlfZXRfYWwvR1NFMTg1OTkxX1JBVy9NMjUvIiwKICBnZW5lLmNvbHVtbiA9IDIsCiAgY2VsbC5jb2x1bW4gPSAxLAogIHVuaXF1ZS5mZWF0dXJlcyA9IFRSVUUsCiAgc3RyaXAuc3VmZml4ID0gRkFMU0UKKQpjb2xuYW1lcyhuYWxkaW5pX3NldXJhdF9tMjUpIDwtIHBhc3RlMCgiTTI1XyIsIGdzdWIoIi0xIiwgIiIsIGNvbG5hbWVzKG5hbGRpbmlfc2V1cmF0X20yNSkpKQoKbmFsZGluaV9zZXVyYXRfbTI2IDwtIFJlYWQxMFgoCiAgZGF0YS5kaXIgPSAibmFsZGluaV9ldF9hbC9HU0UxODU5OTFfUkFXL00yNi8iLAogIGdlbmUuY29sdW1uID0gMiwKICBjZWxsLmNvbHVtbiA9IDEsCiAgdW5pcXVlLmZlYXR1cmVzID0gVFJVRSwKICBzdHJpcC5zdWZmaXggPSBGQUxTRQopCmNvbG5hbWVzKG5hbGRpbmlfc2V1cmF0X20yNikgPC0gcGFzdGUwKCJNMjZfIiwgZ3N1YigiLTEiLCAiIiwgY29sbmFtZXMobmFsZGluaV9zZXVyYXRfbTI2KSkpCgojIGNvbWJpbmUgY2FwdHVyZXMgYW5kIG1lcmdlCm5hbGRpbmlfc2V1cmF0X3B0MDIgPC0gY2JpbmQobmFsZGluaV9zZXVyYXRfbTI1LCBuYWxkaW5pX3NldXJhdF9tMjYpCm5hbGRpbmlfc2V1cmF0X3B0MDIgPC0gQ3JlYXRlU2V1cmF0T2JqZWN0KGNvdW50cyA9IG5hbGRpbmlfc2V1cmF0X3B0MDIsIG1pbi5jZWxscyA9IDAsIG1pbi5mZWF0dXJlcyA9IDIwMCkKbmNvbChuYWxkaW5pX3NldXJhdF9wdDAyKQoKbmFsZGluaV9zZXVyYXRfbTI1IDwtIE5VTEwKbmFsZGluaV9zZXVyYXRfbTI2IDwtIE5VTEwKZ2MoKQoKIyBzdWJzZXQgdG8gY2VsbHMgcHJlc2VudCBpbiBtZXRhZGF0YQpuYWxkaW5pX3NldXJhdF9wdDAyIDwtIHN1YnNldChuYWxkaW5pX3NldXJhdF9wdDAyLCBjZWxscyA9IHJvd25hbWVzKG1ldGFkYXRhX25wbTFfY2VsbHMpKQpuY29sKG5hbGRpbmlfc2V1cmF0X3B0MDIpCgojIGFkZCBtZXRhZGF0YSB0byBTZXVyYXQgb2JqZWN0Cm5hbGRpbmlfc2V1cmF0X3B0MDIgPC0gQWRkTWV0YURhdGEobmFsZGluaV9zZXVyYXRfcHQwMiwgbWV0YWRhdGFfbnBtMV9jZWxscykKdGFibGUobmFsZGluaV9zZXVyYXRfcHQwMiRvcmlnLmlkZW50KQpgYGAKCmBgYHtyfQpuYWxkaW5pX3NldXJhdF9tMjEgPC0gUmVhZDEwWCgKICBkYXRhLmRpciA9ICJuYWxkaW5pX2V0X2FsL0dTRTE4NTk5MV9SQVcvTTIxLyIsCiAgZ2VuZS5jb2x1bW4gPSAyLAogIGNlbGwuY29sdW1uID0gMSwKICB1bmlxdWUuZmVhdHVyZXMgPSBUUlVFLAogIHN0cmlwLnN1ZmZpeCA9IEZBTFNFCikKY29sbmFtZXMobmFsZGluaV9zZXVyYXRfbTIxKSA8LSBwYXN0ZTAoIk0yMV8iLCBnc3ViKCItMSIsICIiLCBjb2xuYW1lcyhuYWxkaW5pX3NldXJhdF9tMjEpKSkKCm5hbGRpbmlfc2V1cmF0X20yMiA8LSBSZWFkMTBYKAogIGRhdGEuZGlyID0gIm5hbGRpbmlfZXRfYWwvR1NFMTg1OTkxX1JBVy9NMjIvIiwKICBnZW5lLmNvbHVtbiA9IDIsCiAgY2VsbC5jb2x1bW4gPSAxLAogIHVuaXF1ZS5mZWF0dXJlcyA9IFRSVUUsCiAgc3RyaXAuc3VmZml4ID0gRkFMU0UKKQpjb2xuYW1lcyhuYWxkaW5pX3NldXJhdF9tMjIpIDwtIHBhc3RlMCgiTTIyXyIsIGdzdWIoIi0xIiwgIiIsIGNvbG5hbWVzKG5hbGRpbmlfc2V1cmF0X20yMikpKQoKbmFsZGluaV9zZXVyYXRfbTIzIDwtIFJlYWQxMFgoCiAgZGF0YS5kaXIgPSAibmFsZGluaV9ldF9hbC9HU0UxODU5OTFfUkFXL00yMy8iLAogIGdlbmUuY29sdW1uID0gMiwKICBjZWxsLmNvbHVtbiA9IDEsCiAgdW5pcXVlLmZlYXR1cmVzID0gVFJVRSwKICBzdHJpcC5zdWZmaXggPSBGQUxTRQopCmNvbG5hbWVzKG5hbGRpbmlfc2V1cmF0X20yMykgPC0gcGFzdGUwKCJNMjNfIiwgZ3N1YigiLTEiLCAiIiwgY29sbmFtZXMobmFsZGluaV9zZXVyYXRfbTIzKSkpCgpuYWxkaW5pX3NldXJhdF9tMjQgPC0gUmVhZDEwWCgKICBkYXRhLmRpciA9ICJuYWxkaW5pX2V0X2FsL0dTRTE4NTk5MV9SQVcvTTI0LyIsCiAgZ2VuZS5jb2x1bW4gPSAyLAogIGNlbGwuY29sdW1uID0gMSwKICB1bmlxdWUuZmVhdHVyZXMgPSBUUlVFLAogIHN0cmlwLnN1ZmZpeCA9IEZBTFNFCikKY29sbmFtZXMobmFsZGluaV9zZXVyYXRfbTI0KSA8LSBwYXN0ZTAoIk0yNF8iLCBnc3ViKCItMSIsICIiLCBjb2xuYW1lcyhuYWxkaW5pX3NldXJhdF9tMjQpKSkKCiMgY29tYmluZSBjYXB0dXJlcyBhbmQgbWVyZ2UKbmFsZGluaV9zZXVyYXRfcHQwNyA8LSBjYmluZChuYWxkaW5pX3NldXJhdF9tMjEsIG5hbGRpbmlfc2V1cmF0X20yMiwgbmFsZGluaV9zZXVyYXRfbTIzLCBuYWxkaW5pX3NldXJhdF9tMjQpCm5hbGRpbmlfc2V1cmF0X3B0MDcgPC0gQ3JlYXRlU2V1cmF0T2JqZWN0KGNvdW50cyA9IG5hbGRpbmlfc2V1cmF0X3B0MDcsIG1pbi5jZWxscyA9IDAsIG1pbi5mZWF0dXJlcyA9IDIwMCkKbmNvbChuYWxkaW5pX3NldXJhdF9wdDA3KQoKbmFsZGluaV9zZXVyYXRfbTIxIDwtIE5VTEwKbmFsZGluaV9zZXVyYXRfbTIyIDwtIE5VTEwKbmFsZGluaV9zZXVyYXRfbTIzIDwtIE5VTEwKbmFsZGluaV9zZXVyYXRfbTI0IDwtIE5VTEwKZ2MoKQoKIyBzdWJzZXQgdG8gY2VsbHMgcHJlc2VudCBpbiBtZXRhZGF0YQpuYWxkaW5pX3NldXJhdF9wdDA3IDwtIHN1YnNldChuYWxkaW5pX3NldXJhdF9wdDA3LCBjZWxscyA9IHJvd25hbWVzKG1ldGFkYXRhX25wbTFfY2VsbHMpKQpuY29sKG5hbGRpbmlfc2V1cmF0X3B0MDcpCgojIGFkZCBtZXRhZGF0YSB0byBTZXVyYXQgb2JqZWN0Cm5hbGRpbmlfc2V1cmF0X3B0MDcgPC0gQWRkTWV0YURhdGEobmFsZGluaV9zZXVyYXRfcHQwNywgbWV0YWRhdGFfbnBtMV9jZWxscykKdGFibGUobmFsZGluaV9zZXVyYXRfcHQwNyRvcmlnLmlkZW50KQpgYGAKCgoKRmlsdGVyIGNlbGxzIGZvciA+MzAwMCBVTUksIDEwMDAgZ2VuZXMsIDwxMCUgbWl0b2Nob25kcmlhbCB0cmFuc2NyaXB0cyBQVDAyLgpQVDA3IHNlZW1zIHRvIGhhdmUgbG93ZXIgcXVhbGl0eS4gVXNlIGxlc3Mgc3RyaW5nZW50IGZpbHRlcnMuIAoKYGBge3J9ClZsblBsb3QobmFsZGluaV9zZXVyYXRfcHQwMiwgZmVhdHVyZXMgPSBjKCJuQ291bnRfUk5BIiwgIm5GZWF0dXJlX1JOQSIsICJwZXJjZW50Lm10IiksIGxvZyA9IFQsIHB0LnNpemUgPSAwKQpWbG5QbG90KG5hbGRpbmlfc2V1cmF0X3B0MDcsIGZlYXR1cmVzID0gYygibkNvdW50X1JOQSIsICJuRmVhdHVyZV9STkEiLCAicGVyY2VudC5tdCIpLCBsb2cgPSBULCBwdC5zaXplID0gMCkKYGBgCgpTZWVtcyBsaWtlIFBUMDIgaXMgZmVtYWxlLCBQVDA3IGlzIG1hbGUuIAoKYGBge3J9ClZsblBsb3QobmFsZGluaV9zZXVyYXRfcHQwMiwgZmVhdHVyZXMgPSBjKCJYSVNUIikpClZsblBsb3QobmFsZGluaV9zZXVyYXRfcHQwNywgZmVhdHVyZXMgPSBjKCJYSVNUIikpCmBgYAoKCmBgYHtyfQpuYWxkaW5pX3NldXJhdF9wdDAyIDwtCiAgc3Vic2V0KG5hbGRpbmlfc2V1cmF0X3B0MDIsCiAgICAgICAgIHN1YnNldCA9IG5Db3VudF9STkEgPiAzMDAwICYgbkZlYXR1cmVfUk5BID4gMTAwMCAmIHBlcmNlbnQubXQgPCAxMCkKbmFsZGluaV9zZXVyYXRfcHQwNyA8LQogIHN1YnNldChuYWxkaW5pX3NldXJhdF9wdDA3LAogICAgICAgICBzdWJzZXQgPSBuQ291bnRfUk5BID4gMTAwMCAmIG5GZWF0dXJlX1JOQSA+IDUwMCAmIHBlcmNlbnQubXQgPCAxMCkKYGBgCgoKYGBge3J9CnRhYmxlKG5hbGRpbmlfc2V1cmF0X3B0MDIkb3JpZy5pZGVudCkKdGFibGUobmFsZGluaV9zZXVyYXRfcHQwNyRvcmlnLmlkZW50KQpgYGAKCmBgYHtyfQpuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDcgPC0KICBtZXJnZShuYWxkaW5pX3NldXJhdF9wdDAyLCBuYWxkaW5pX3NldXJhdF9wdDA3KQpuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDcKCm5hbGRpbmlfc2V1cmF0X3B0MDJfcHQwNyA8LQogIG1lcmdlKAogICAgbmFsZGluaV9zZXVyYXRfcHQwMiwKICAgIHkgPSBuYWxkaW5pX3NldXJhdF9wdDA3LAogICAgYWRkLmNlbGwuaWRzID0gYygiUFQwMiIsICJQVDA3IiksCiAgICBwcm9qZWN0ID0gIk5hbGRpbmkiCiAgKQpuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDcKYGBgCgpOb3JtYWxpemUgZ2VuZSBleHByZXNzaW9uLiAKCmBgYHtyfQpuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDcgPC0gTm9ybWFsaXplRGF0YShuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDcpCmBgYAoKCiMjIyMjIFByZWRpY3QgSFNQQyBjZWxsIHR5cGUKCmBgYHtyfQpYIDwtCiAgY2JpbmQoCiAgICBuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDdAYXNzYXlzJFJOQUBsYXllcnMkZGF0YS4xLAogICAgbmFsZGluaV9zZXVyYXRfcHQwMl9wdDA3QGFzc2F5cyRSTkFAbGF5ZXJzJGRhdGEuMgogICkKcm93bmFtZXMoWCkgPC0gcm93bmFtZXMobmFsZGluaV9zZXVyYXRfcHQwMl9wdDA3KQpgYGAKClNvbWUgZmVhdHVyZXMgdXNlZCBpbiBjbGFzc2lmaWVyIGFyZSBtaXNzaW5nIGluIGRhdGEsIGVpdGhlciBkdWUgdG8gZ2VuZSBJRCBtaXNtYXRjaGVzIG9yIGJlY2F1c2UgdGhlIGdlbmVzIGFyZSBub3QgZXhwcmVzc2VkLiAKQm90aCB2YW4gR2FsZW4gYW5kIE5hbGRpbmkgd2VyZSBhbGlnbmVkIHRvIGhnMzgsIHNvIGl0IGlzIG1vcmUgbGlrZWx5IGxhY2sgb2YgZXhwcmVzc2lvbi4gCldpbGwgc2V0IG1pc3NpbmcgZ2VuZXMgdG8gMC4gCgpgYGB7cn0KZmVhdHVyZXNfbWlzc2luZyA8LQogIHJvd25hbWVzKHJmLmNlbGx0eXBlLmlubmVyJGltcG9ydGFuY2UpWyFyb3duYW1lcyhyZi5jZWxsdHlwZS5pbm5lciRpbXBvcnRhbmNlKSAlaW4lIHJvd25hbWVzKFgpXQoKZmVhdHVyZXNfbWlzc2luZ19tYXQgPC0KICBtYXRyaXgobnJvdyA9IGxlbmd0aChmZWF0dXJlc19taXNzaW5nKSwKICAgICAgICAgbmNvbCA9IG5jb2woWCksCiAgICAgICAgIGRhdGEgPSAwKQpyb3duYW1lcyhmZWF0dXJlc19taXNzaW5nX21hdCkgPC0gZmVhdHVyZXNfbWlzc2luZwpjb2xuYW1lcyhmZWF0dXJlc19taXNzaW5nX21hdCkgPC0gY29sbmFtZXMoWCkKClggPC0gcmJpbmQoWCwgZmVhdHVyZXNfbWlzc2luZ19tYXQpCgpYIDwtIFhbcm93bmFtZXMocmYuY2VsbHR5cGUuaW5uZXIkaW1wb3J0YW5jZSksIF0KYGBgCgoKYGBge3J9Cm5hbGRpbmlfc2V1cmF0X3B0MDJfcHQwN19jZWxsdHlwZV9wcmVkIDwtIHByZWRpY3QocmYuY2VsbHR5cGUuaW5uZXIsIG5ld2RhdGEgPSB0KFgpLCB0eXBlID0gInByb2IiKQpgYGAKCiMjIyMjIFByZWRpY3QgbWFsaWduYW50IGNlbGxzCgpgYGB7cn0KWCA8LQogIGNiaW5kKAogICAgbmFsZGluaV9zZXVyYXRfcHQwMl9wdDA3QGFzc2F5cyRSTkFAbGF5ZXJzJGRhdGEuMSwKICAgIG5hbGRpbmlfc2V1cmF0X3B0MDJfcHQwN0Bhc3NheXMkUk5BQGxheWVycyRkYXRhLjIKICApClggPC0gcmJpbmQoWCwgdChuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDdfY2VsbHR5cGVfcHJlZCkpCnJvd25hbWVzKFgpIDwtCiAgYygKICAgIHJvd25hbWVzKG5hbGRpbmlfc2V1cmF0X3B0MDJfcHQwNyksCiAgICBjb2xuYW1lcyhuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDdfY2VsbHR5cGVfcHJlZCkKICApCgpmZWF0dXJlc19taXNzaW5nIDwtCiAgcm93bmFtZXMocmYubWFsaWduYW50LmNlbGx0eXBlJGltcG9ydGFuY2UpWyFyb3duYW1lcyhyZi5tYWxpZ25hbnQuY2VsbHR5cGUkaW1wb3J0YW5jZSkgJWluJSByb3duYW1lcyhYKV0KZmVhdHVyZXNfbWlzc2luZ19tYXQgPC0KICBtYXRyaXgobnJvdyA9IGxlbmd0aChmZWF0dXJlc19taXNzaW5nKSwKICAgICAgICAgbmNvbCA9IG5jb2woWCksCiAgICAgICAgIGRhdGEgPSAwKQpyb3duYW1lcyhmZWF0dXJlc19taXNzaW5nX21hdCkgPC0gZmVhdHVyZXNfbWlzc2luZwpjb2xuYW1lcyhmZWF0dXJlc19taXNzaW5nX21hdCkgPC0gY29sbmFtZXMoWCkKClggPC0gcmJpbmQoWCwgZmVhdHVyZXNfbWlzc2luZ19tYXQpCmBgYAoKCmBgYHtyfQpuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDdfbWFsaWduYW50X3ByZWQgPC0KICBwcmVkaWN0KHJmLm1hbGlnbmFudC5jZWxsdHlwZSwKICAgICAgICAgIG5ld2RhdGEgPSB0KFgpLAogICAgICAgICAgdHlwZSA9ICJwcm9iIikKCm5hbGRpbmlfc2V1cmF0X3B0MDJfcHQwN19tYWxpZ25hbnRfcHJlZF9tYXggPC0KICBjb2xuYW1lcyhuYWxkaW5pX3NldXJhdF9wdDAyX3B0MDdfbWFsaWduYW50X3ByZWQpW21heC5jb2wobmFsZGluaV9zZXVyYXRfcHQwMl9wdDA3X21hbGlnbmFudF9wcmVkKV0KYGBgCgoKIyMjIyMgUmVzdWx0cwoKQ29tcGFyZSBtYWxpZ25hbnQgdnMuIGhlYWx0aHkgcHJlZGljdGlvbiB3aXRoIGdlbm90eXBlIGRhdGEuIAoKYGBge3J9Cm5hbGRpbmlfc2V1cmF0X3B0MDJfbWFsaWduYW50X3ByZWRfbWF4IDwtIG5hbGRpbmlfc2V1cmF0X3B0MDJfcHQwN19tYWxpZ25hbnRfcHJlZF9tYXhbbmFsZGluaV9zZXVyYXRfcHQwMl9wdDA3JFBhdGllbnRJRCA9PSAiUFQwMiJdCnRhYmxlKAogIG5hbGRpbmlfc2V1cmF0X3B0MDJfbWFsaWduYW50X3ByZWRfbWF4LAogIG5hbGRpbmlfc2V1cmF0X3B0MDJAbWV0YS5kYXRhJENsYXNzaWZpY2F0aW9uCikKCnJvdW5kKHQodCgKICB0YWJsZSgKICAgIG5hbGRpbmlfc2V1cmF0X3B0MDJfbWFsaWduYW50X3ByZWRfbWF4LAogICAgbmFsZGluaV9zZXVyYXRfcHQwMkBtZXRhLmRhdGEkQ2xhc3NpZmljYXRpb24KICApCikgLyBhcy52ZWN0b3IoCiAgdGFibGUobmFsZGluaV9zZXVyYXRfcHQwMkBtZXRhLmRhdGEkQ2xhc3NpZmljYXRpb24pCikpLCAzKQpgYGAKCmBgYHtyfQpuYWxkaW5pX3NldXJhdF9wdDA3X21hbGlnbmFudF9wcmVkX21heCA8LQogIG5hbGRpbmlfc2V1cmF0X3B0MDJfcHQwN19tYWxpZ25hbnRfcHJlZF9tYXhbbmFsZGluaV9zZXVyYXRfcHQwMl9wdDA3JFBhdGllbnRJRCA9PSAiUFQwNyJdCnRhYmxlKAogIG5hbGRpbmlfc2V1cmF0X3B0MDdfbWFsaWduYW50X3ByZWRfbWF4LAogIG5hbGRpbmlfc2V1cmF0X3B0MDdAbWV0YS5kYXRhJENsYXNzaWZpY2F0aW9uCikKCnJvdW5kKHQodCgKICB0YWJsZSgKICAgIG5hbGRpbmlfc2V1cmF0X3B0MDdfbWFsaWduYW50X3ByZWRfbWF4LAogICAgbmFsZGluaV9zZXVyYXRfcHQwN0BtZXRhLmRhdGEkQ2xhc3NpZmljYXRpb24KICApCikgLyBhcy52ZWN0b3IoCiAgdGFibGUobmFsZGluaV9zZXVyYXRfcHQwN0BtZXRhLmRhdGEkQ2xhc3NpZmljYXRpb24pCikpLCAzKQpgYGAKCgpUZXN0IG9uIHNhbXBsZSB3aXRoIGEgbXV0YXRpb24gbm90IHByZXNlbnQgaW4gdmFuIEdhbGVuIGRhdGEuIAoKZGVsKDcpIEFNTDogZGVsZXRpb24gaW4gY2hyb21vc29tZSA3LiAKCkNsYXNzaWZpY2F0aW9uIG9mIGRlbCg3KSBjZWxscyBpbnRvIG1hbGlnbmFudC93dDogIAoiV2UgbGV2ZXJhZ2VkIHRoZSBBZGRNb2R1bGVTY29yZSBmdW5jdGlvbiBmb3IgZXZhbHVhdGluZyB0aGUgZXhwcmVzc2lvbiBsZXZlbCBvZiBhIENociA3IHNpZ25hdHVyZSBieSB1c2luZyBhcyBpbnB1dCBnZW5lIGxpc3QgYWxsIGdlbmVzIGxvY2F0ZWQgb24gaXQuIFRoZSBvYnNlcnZlZCBkaXN0cmlidXRpb24gb2YgQ2hyIDcgbW9kdWxlIHNjb3JlcyBpbiB0aGUgZGF0YXNldHMgZm9sbG93ZWQgYSBiaW1vZGFsIGRpc3RyaWJ1dGlvbiBhbGxvd2luZyB1cyB0byBjbGFzc2lmeSBjZWxscyBhcyBBTUwgb3Igbm9uLUFNTCBieSBydW5uaW5nIGEgay1tZWFucyBjbHVzdGVyaW5nIChuID0gMikgb24gdGhlIHZlY3RvciBvZiBDaHIgNyBzaWduYXR1cmUgc2NvcmVzIGFuZCBsYWJlbGxpbmcgY2VsbHMgaW4gdGhlIGhpZ2ggc2NvcmUgZ3JvdXAgYXMgbm9uLUFNTCBhbmQgdGhvc2UgaW4gdGhlIGxvdyBzY29yZSBncm91cCBhcyBBTUwuIgoKVW5mb3J0dW5hdGVseSwgbm90IGluY2x1ZGVkIGluIG1ldGFkYXRhLgoKUFQxMSwgUFQxNzogcmVmcmFjdG9yeSBkaXNlYXNlOyBQVDE4OiBlYXJseSByZWxhcHNlCgpgYGB7cn0KbWV0YWRhdGFfZGVsN19jZWxscyA8LSByZWFkUkRTKCJuYWxkaW5pX2V0X2FsL0dTRTE4NTk5MV9BTUxfRGVsN19tZXRhZGF0YS5yZHMiKQoKdGFibGUobWV0YWRhdGFfZGVsN19jZWxscyRvcmlnLmlkZW50LCBtZXRhZGF0YV9kZWw3X2NlbGxzJFBhdGllbnRJRCkKYGBgCgpDb21wdXRpbmcgbW9kdWxlIHNjb3JlIHdpdGggYWxsIGdlbmVzIG9uIGNocjcgbWlnaHQgYmUgdmVyeSBzbG93LiAKCkRvd25sb2FkIGNlbGxyYW5nZXIgZ3RmIHJlZmVyZW5jZSwgZ2V0IGFsbCBnZW5lcyBvbiBjaHI3LgoKYGBge3NoIGV2YWw9RkFMU0V9CnNvdXJjZT0iL1VzZXJzL2hvbHplaGVucmlldHRhL0RvY3VtZW50cy9wZXJzb25hbC9waGRfYXBwbGljYXRpb25zL211ZHNfdGFza19tYXJyL3JlZmVyZW5jZV9zb3VyY2VzIgpta2RpciAtcCAiJHNvdXJjZSIKZ3RmX3VybD0iaHR0cDovL2Z0cC5lYmkuYWMudWsvcHViL2RhdGFiYXNlcy9nZW5jb2RlL0dlbmNvZGVfaHVtYW4vcmVsZWFzZV80NC9nZW5jb2RlLnY0NC5wcmltYXJ5X2Fzc2VtYmx5LmFubm90YXRpb24uZ3RmLmd6IgpndGZfaW49IiR7c291cmNlfS9nZW5jb2RlLnY0NC5wcmltYXJ5X2Fzc2VtYmx5LmFubm90YXRpb24uZ3RmIgoKaWYgWyAhIC1mICIkZ3RmX2luIiBdOyB0aGVuCiAgICBjdXJsIC1zUyAiJGd0Zl91cmwiIHwgemNhdCA+ICIkZ3RmX2luIgpmaQoKZ3JlcCAiXmNocjciICRndGZfaW4gfCBhd2sgJyQzID09ICJnZW5lIicgfCBzZWQgJ3MvLipnZW5lX25hbWUgIlwoW14iXSpcKSIuKi9cMS9nJyA+ICR7c291cmNlfS9nZW5jb2RlLnY0NF9nZW5lX25hbWVzX2NocjcudHh0CgpncmVwICJeY2hyWSIgJGd0Zl9pbiB8IGF3ayAnJDMgPT0gImdlbmUiJyB8IHNlZCAncy8uKmdlbmVfbmFtZSAiXChbXiJdKlwpIi4qL1wxL2cnID4gJHtzb3VyY2V9L2dlbmNvZGUudjQ0X2dlbmVfbmFtZXNfY2hyWS50eHQKYGBgCgpgYGB7cn0KY2hyN19nZW5lcyA8LQogIHJlYWRfbGluZXMoCiAgICAiL1VzZXJzL2hvbHplaGVucmlldHRhL0RvY3VtZW50cy9wZXJzb25hbC9waGRfYXBwbGljYXRpb25zL211ZHNfdGFza19tYXJyL3JlZmVyZW5jZV9zb3VyY2VzL2dlbmNvZGUudjQ0X2dlbmVfbmFtZXNfY2hyNy50eHQiCiAgKQpgYGAKClN1YnNldCB0byBnZW5lcyBleHByZXNzZWQgaW4gPj0gMTAlIG9mIGNlbGxzIGluIGRlbCg3KSBzY1JOQS1zZXEgZGF0YSB0byBzcGVlZCB1cCBhbmFseXNpcy4KCmBgYHtyfQoKYGBgCgoKIyMgU2F2ZSBTZXNzaW9uIEluZm8KCmBgYHtyfQp3cml0ZUxpbmVzKGNhcHR1cmUub3V0cHV0KHNlc3Npb25JbmZvKCkpLCBwYXN0ZTAoU3lzLkRhdGUoKSwgIl9zZXNzaW9uSW5mby50eHQiKSkKYGBgCgo=